Проблема, с которой вы столкнулись, заключается в том, что бит typeof myObject === "object"
сообщает компилятору, что экземпляр myObject является , по сути, объектом. Таким образом, TypeScript предполагает в остальной части выражения, что myObject имеет тип {}
, который явно не является.
Я бы отпустил unknown
в этом методе и применил бы KISS:
function isDataObject(myObject: any): myObject is DataObject {
return myObject && typeof myObject.data === "string";
}
Редактировать после долгих обсуждений с Зеркмсом и Адамом. Ну, я довольно упрямый, поэтому я продолжал выбирать решения zerkms. Оказывается, может быть более безопасным способом создания Type Guards, и я собираюсь предложить его в следующем.
Моя идея состоит в том, чтобы использовать проверки времени компиляции компилятора дляпроверьте «план» типа охранника. Я собираюсь определить ValidatorDefinition следующим образом:
export type ValidatorTypes =
| "string"
| "boolean"
| "number"
| "array"
| "string?"
| "boolean?"
| "number?";
export type ValidatorDefinition<T> = {
[key in keyof T]: T[key] extends string
? "string" | "string?"
: T[key] extends number
? "number" | "number?"
: T[key] extends boolean
? "boolean" | "boolean?"
: (T[key] extends Array<infer TArrayItem>
? Array<ValidatorDefinition<TArrayItem>>
: ValidatorTypes);
};
Таким образом, каждое свойство простого типа должно быть связано со строкой, точно определяющей тип проверки.
Затем яСобираемся построить фабрику TypeGuard:
function typeGuardFactory<T>(
reference: ValidatorDefinition<T>
): (value: any) => value is T {
const validators: ((propertyValue: any) => boolean)[] = Object.keys(
reference
).map(key => {
const referenceValue = (<any>reference)[key];
switch (referenceValue) {
case "string":
return v => typeof v[key] === "string";
case "boolean":
return v => typeof v[key] === "boolean";
case "number":
return v => typeof v[key] === "number";
case "string?":
return v => v[key] == null || typeof v[key] === "string";
case "boolean?":
return v => v[key] == null || typeof v[key] === "boolean";
case "number?":
return v => v[key] == null || typeof v[key] === "number";
default:
// we are not accepting null/undefined for empty array... Should decide how to
// handle/configure the specific case
if (Array.isArray(referenceValue)) {
const arrayItemValidator = typeGuardFactory<any>(referenceValue[0]);
return v => Array.isArray(v[key]) && v[key].every(arrayItemValidator);
}
// TODO: handle default case
return _v => false;
}
});
return (value: T): value is T =>
(value && validators.every(validator => validator(value))) || false;
}
Пример использования:
type DataObject = {
data: string
};
const hasDataObject = typeGuardFactory<DataObject>({ data: "string" });
Приятно то, что если вы ошибетесь в определении Validator, вы получите ошибки времени компиляции.
Какой-то простой код модульного тестирования:
const validatorForDataObject = typeGuardFactory({ data: "string" });
const testCases: { value: any; expected: boolean }[] = [
{ value: null, expected: false },
{ value: undefined, expected: false },
{ value: { data: 0 }, expected: false },
{ value: { data: "0" }, expected: true }
];
testCases.forEach(testCase => {
const testResult = validatorForDataObject(testCase.value);
if (testResult === testCase.expected) {
console.info(`Success (with value ${testResult})`, testCase.value);
} else {
console.error(`Fail (with value ${testResult})`, testCase.value);
}
});
interface AnotherType {
stringProp: string;
numberProp: number;
booleanProp: boolean;
nullableStringProp?: string;
nullableNumberProp?: number;
nullableBooleanProp?: boolean;
arrayProp: {
data: string;
}[];
}
const validatorForAnotherType = typeGuardFactory<AnotherType>({
stringProp: "string",
numberProp: "number",
booleanProp: "boolean",
nullableStringProp: "string?",
nullableNumberProp: "number?",
nullableBooleanProp: "boolean?",
arrayProp: [
{
data: "string"
}
]
});
const testCases2: { value: any; expected: boolean }[] = [
{ value: null, expected: false },
{ value: undefined, expected: false },
{ value: { data: 0 }, expected: false },
{
value: {
stringProp: "string",
numberProp: 1,
booleanProp: true,
nullableStringProp: "string",
nullableNumberProp: 1,
nullableBooleanProp: false,
arrayProp: [{ data: "" }]
},
expected: true
},
{
value: {
stringProp: "string",
numberProp: 1,
booleanProp: true,
nullableStringProp: null,
nullableNumberProp: null,
nullableBooleanProp: null,
arrayProp: []
},
expected: true
},
{
value: {
stringProp: "string",
numberProp: 1,
booleanProp: true,
nullableStringProp: "string",
nullableNumberProp: null,
nullableBooleanProp: null,
arrayProp: [{ data: "" }, { data: "" }, { data: 0 }]
},
expected: false
},
{
value: {
stringProp: "string",
numberProp: 1,
booleanProp: true,
nullableStringProp: "string",
nullableNumberProp: null,
nullableBooleanProp: null,
arrayProp: [{ data: "" }, { data: "" }, { data: 0 }]
},
expected: false
},
{
value: {
stringProp: null,
numberProp: 1,
booleanProp: true,
nullableStringProp: null,
nullableNumberProp: null,
nullableBooleanProp: null,
arrayProp: []
},
expected: false
}
];
testCases2.forEach(testCase => {
const testResult = validatorForAnotherType(testCase.value);
if (testResult === testCase.expected) {
console.info(`Success (with value ${testResult})`, testCase.value);
} else {
console.error(`Fail (with value ${testResult})`, testCase.value);
}
});
Самое смешное, что код проверки все еще широко используется any
, но все остальное в мире фарфора за пределамиохранник кажется довольно твердым. Это просто набросок, но я думаю, что он показывает некоторый потенциал.
Полный фрагмент .