Search code examples
javascripttypescripttypestypeguards

Typeguarding an unknown nested object


I want to create a type guard for an error that I know is a nested object like this:

const myError = {
   status: 404,
   data: {
        foo: "bar"
       }
}

The error, if it exists will initially be determined as an unknown type by the library returning the error. I want to create a custom type guard that checks the error and converts its type to something more appropriate.

Here's what I've tried so far:

type MyError = {
    status: number,
    data: {
        foo: string
        }
}

const myErrorTypeguard = (myError: unknown): myError is MyError => {
    return (typeof myError === 'object'
    && myError !== null
    && 'status' in myError
    && 'data' in myError
    && 'foo' in myError.data)
}

This results in an error from the typescript compiler prompting that:

Property 'data' does not exist on type 'object'.

What's the correct way to type guard this kind of an unknown nested object?


Solution

  • TS 4.9+

    TypeScript 4.9 offers better narrowing with in, as you can read in their announcements here.

    return (typeof myError === 'object'
        && myError !== null
        && 'status' in myError
        && 'data' in myError // myError is now of type object & Record<"data", unknown>
        // the icky part: TypeScript requires us to do this... which is understandable
        && typeof myError.data === "object" && myError.data !== null
        && 'foo' in myError.data) // so now this works
    

    Playground (using 4.9)

    Same playground with 4.8


    TS 4.8-

    Unfortunately there is no easy way around it. You should just do a cast, even if it's ugly:

    return (typeof myError === 'object'
        && myError !== null
        && 'status' in myError
        && 'data' in myError
        && 'foo' in (myError as any).data) // or myError as { data: {} }...?
    

    Maybe even using any instead of unknown as the parameter type:

    const myErrorTypeguard = (myError: any): myError is MyError => {
    //                        ~~~~~~~  ^^^