Typescript how to check the type of an object with multiple types

Viewed 1216

I have the following code in typescript

Array.map((r: number | PointOptionsObject | [string, number | null] | null ) => {
                    if (r) {
                      ;`
                      ${r.phrase}:
                      <span> hello </span>
                      `
                    }
                  })

I am getting an error in typescript, property phrase doesn't exist on type number essentially the only data that I am using is of type PointOptionsObject which has the phrase property.. But I am forced to use this long declaration for each of the data items in the array's type because it is the type of the built in library. If I change my code to have

if(typeof r !== "number")

Then I get a new error Property 'phrase' does not exist on type '[string, number | null]'

And here is where I am stuck, I am not sure how to check the type of this and we cant using the built in typeof operator. How can I check that the value of r is only the PointOptionsObject?

2 Answers

You can eliminate all invalid types by adding another type guard for arrays, using the built in array method Array.isArray:

type PointOptionsObject = {
  phrase: string;
}

type DataTypes = number | PointOptionsObject | [string, number | null] | null;

const result = [].map((r: DataTypes ) => {
  if (r && typeof r !== "number" && !Array.isArray(r)) {
    ;`
    ${r.phrase}:
    <span> hello </span>
    `
  }
})

Alternatively you could write your own type guard which checks for PointOptionsObject specifically. Extend this to cover all properties that the object actually has:

const isPointOptionsObject = (o: any): o is PointOptionsObject => {
  return o && o.hasOwnProperty("phrase") && typeof o.phrase === "string";
}

And use it like:

const result = [].map((r: DataTypes ) => {
  if (isPointOptionsObject(r)) {
    ;`
    ${r.phrase}:
    <span> hello </span>
    `
  }
})

To look a little cleaner, you could create a util function as @lawrence-witt suggested, but like this:

export function isPointOptionsObject(obj: any): boolean {
  try {
    const _ = obj as PointOptionsObject
    return true
  } catch {
    return false
  }
}

This is more extensible too because you can change PointOptionsObject and the util function still checks accurately.

Or to be even more reusable, you could do:

export function isOfType<T>(obj): boolean {
    try {
        const _ = obj as T
        return true
    } catch {
        return false
    }
}

and use it like if (isOfType<PointOptionsObject>(obj)

Related