Reputation: 3761
I have the following problem. Let's say I have a type:
type A = {
prop1: string,
prop2: {
prop3: string
}
}
I am receiving some json object from an external service and I want to validate if that json matches type A
:
function isA(obj:any): boolean {
// What should be here?
}
So if my obj
is something like:
{
prop1: "Hello",
prop2: {
prop3: "World"
}
}
or
{
prop1: "Hello",
prop2: {
prop3: "World"
},
moreProps: "I don't care about"
}
The function would return true, but false for something like
{
foo: "Hello",
bar: {
prop3: "World"
}
}
What's the easiest way to achieve this?
Thanks.
Upvotes: 17
Views: 24506
Reputation: 11
Not a perfect solution either, but another option is to define a "template object" against which you can perform a runtime comparison like this:
// A const object used to define a type and to serve as a template for runtime comparison.
const myTemplateObject = {
prop1: "",
prop2: 12,
prop3: 14 as string | number,
prop4: {
potatoes: "",
carrots: 0,
},
};
// So you can use the type in the rest of your code. Or just define it explicitly and make the object above an instance of it.
export type myType = typeof myTemplateObject;
export function matchesType(
object: Record<string, unknown>,
templateObject: Record<string, unknown>,
) {
for (const key in templateObject) {
const templatePropValue = templateObject[key];
const templatePropType = templatePropValue;
switch (templatePropType) {
case "function":
// fall-through
case "symbol":
// fall-through
case "undefined":
throw new Error(
`matchesType function does not support template objects with ${templatePropType} fields`,
);
// or return false if you prefer
case "bigint":
case "boolean":
case "number":
case "string":
return templatePropType === typeof object[key];
case "object":
const objectPropValue = object[key];
if (typeof objectPropValue === "object" && objectPropValue !== null) {
return matchesType(
objectPropValue as Record<string, unknown>,
templatePropValue as Record<string, unknown>,
);
} else {
return false;
}
}
}
return true;
}
Upvotes: 1
Reputation: 7542
To use a type-guard, you should change the return type of your isA
function to be obj is A
Overall that should make your type validation function look like:
function isA(obj: unknown): obj is A {
// return boolean here
}
typeof
operator to check propertiestypeof
will return a string value telling you what the type of a variable is. (docs)
In this case, for A you can do something like:
function isA(obj: unknown): obj is A {
return (
obj &&
typeof obj === 'object' &&
typeof obj['prop1'] === 'string' &&
obj['prop2'] &&
typeof obj['prop2'] === 'object' &&
typeof obj['prop2']['prop3'] === 'string'
);
}
It's not the most readable thing in the world, and you could always break it down to it's component pieces and comment each check if you'd like.
One important thing to note, however, is that typeof null
is actually 'object'
so you can't simply check if typeof obj['prop2'] === 'object'
and then move on, you'll need to also check if it exists since it still could be null
.
At this point, not only will you be validating correctly at runtime, but TypeScript will now be able to improve its type-checking by narrowing the type of obj
to A
when isA
returns true.
Upvotes: 9