I have a TypeScript project and have defined a custom interface
interface Person {
name: string;
}
I would like to create a type guard for this interface that only returns true
if a value adheres to that interface. Specifically that it is...
- An Object
- with a
name
property - where the value of
name
is of typestring
I am able to accomplish 1 and 2 as follows:
const isPerson = (value: unknown): value is Person => (
value instanceof Object
&& 'name' in value
)
However if I try to check the type of value.name
...
const isPerson = (value: unknown): value is Person => (
value instanceof Object
&& 'name' in value
&& typeof value.name === 'string'
)
I receive the following error, highlighting .name
Property 'name' does not exist on type 'never'.ts(2339)
How can I create a type guard that ensures not only that a property exists but ALSO that the property is of a certain type?
CodePudding user response:
- You need to use a type-assertion (i.e.
as
) to allow you to hypothetically test object properties in TypeScript.- In my own personal coding style, I always name this local
whatIf
to make it obvious it's not-real.
- In my own personal coding style, I always name this local
- Test each property with the
typeof
operator, which is safe to use with first-level maybe-undefined properties.- Don't forget that
var x = null; typeof x === 'object'
, so you also need to check forx !== null
too.
- Don't forget that
- You don't need to use the
in
operator - and TypeScript cannot usein
to determine property-types, only property-is-not-undefined
.
Something like this:
const isPerson = (value: unknown): value is Person {
const whatIf = value as Person;
return (
( typeof whatIf === 'object' && whatIf !== null )
&&
( typeof whatIf.name === 'string' )
);
}
CodePudding user response:
instanceof Object
should probably be avoided. For example the below code enters the else
clause, even though the compiler infers o
as never
in that clause. Use typeof v === 'object'
instead. Here's a post talking about the difference between Object
(which is inferred from instanceof
) and object
(which is inferred from typeof
).
interface Person {
name: string
}
const o: Object = "a"
if (o instanceof Object) {
console.log(o, "is Object")
} else {
console.log(o, "is not Object")
}
I always try to avoid type assertions, so this is how I would solve it.
const hasName = <T extends {}>(value: T): value is T & {name: unknown} => "name" in value
const isPerson = (value: unknown): value is Person => (
typeof value === 'object'
&& value !== null
&& hasName(value)
&& typeof value.name === 'string'
)
CodePudding user response:
Here you have generic approach:
interface Person {
name: string
}
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
const isPerson = (obj: unknown): obj is Person =>
hasProperty(obj, 'name') && typeof obj.name === 'string'
hasProperty
is a generic utility type for checking/typeguarding properties in objects