1

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...

  1. An Object
  2. with a name property
  3. where the value of name is of type string

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?

slifty
  • 13,062
  • 13
  • 71
  • 109

3 Answers3

1
  • 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.
  • 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 for x !== null too.
  • You don't need to use the in operator - and TypeScript cannot use in 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' )
    );
}
Dai
  • 141,631
  • 28
  • 261
  • 374
1

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

Playground

  • 1
    I upvoted for the clever use of `Record` in an intersection - I'll try to use this approach in future as it's more succinct than my approach, but because it uses `hasOwnProperty` it won't work with inherited prototype members - is there a reason for using `hasOwnProperty` instead of the `in` operator? – Dai Jul 01 '22 at 08:41
  • This is exactly the reason why I have used `Object.prototype.hasOwnProperty.call`. See [this](https://v8.dev/features/object-has-own). If you want to check prototype you can use `prop in object` or `object.hasOwnProperty`. I'm not sure, please test it in runtime. My main focus is on types rather than runtime behaviour, I should have pointed that – captain-yossarian from Ukraine Jul 01 '22 at 11:05
0

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'
)

Anton
  • 1,045
  • 1
  • 7
  • 16