Home > Mobile >  How can I write a typeguard for an interface that checks attribute types?
How can I write a typeguard for an interface that checks attribute types?

Time:06-18

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?

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.
  • 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' )
    );
}

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

Playground

  • Related