Alex Wayne
Alex Wayne

Reputation: 187194

Typescript error when default clause of switch for an unkown union type

I'm reading data from a file, which may have more data in it than my code cares about. I have a typescript type for each object type I want to process. But in the case of some unsupported object type, I want do something with that object.

The problem is that typescript thinks I have exhausted all possibilities and that my default clause is impossible to get to.

// Untyped example data source.
function loadFromSomeFile(): any {
  return [{ objType: "A", a: 1 }, { objType: "B", b: 2 }] as any
}

// Union type of supported data from data source.
type A = { objType: "A", a: number }
type B = { objType: "B", b: number }
type ObjTypes = A | B

// Load the data.
const arr: ObjTypes[] = loadFromSomeFile()

// Switch on the type of each object. 
for (const obj of arr) {
  switch (obj.objType) {
    case "A":
      console.log('A', obj.a)
      break
    case "B":
      console.log('B', obj.b)
      break
    default:
      // Fall though case for unsupported objType
      console.log('unkown objType: ' + obj.objType)
      // ^ TS Error: objType does not exist on type 'never'
  }
}

Error on Typescript Playground

I thought of trying to add a third option to the union like:

type X = { objType: string } // unknown
type ObjTypes = A | B | X

But now when obj.objType === 'A' typescript can't tell if it's an A or an X since it's a valid type for both.

How can I tell typescript that there may also be unknown and unhandled values, and that my list is not exhaustive?

Upvotes: 3

Views: 324

Answers (3)

Alex Wayne
Alex Wayne

Reputation: 187194

After wrestling with this for a while, I decided to give the unknown types a concrete type with a fixed value, that is incorrect.

type A = { objType: "A", a: number }
type B = { objType: "B", b: number }
type Unknown = { objType: '___unknown-obj-type___' }
type ObjTypes = A | B | Unknown

This causes the default clause of the switch fallthrough to this type. And I can still access the objects properties at runtime for real values.

It feels a bit like a hack but this has the least amount of code readability compromises so far, IMHO.

Typescript Playground

Upvotes: 1

Yakov Fain
Yakov Fain

Reputation: 12376

Since you can get not only A or B from the file, don't declare the type as A | B.

See the Playground

Upvotes: 0

djs
djs

Reputation: 1700

Using:

return [
    { objType: "A", a: 1 }, 
    { objType: "B", b: 2 },
    { objType: "C" }
    { foo: "bar" }
] as any

as the data, and setting the type to be:

type ObjTypes = A | B | any

Then, inside each of your case blocks, cast the object to the type expected, either A or B, etc.

case "A":
  let a = <A>obj;
  console.log('A', a.a)
  //console.log('A', a.x) // TS error! 
  break

logs to the console:

A 1
B 2
unknown objType: C
unknown objType: undefined

See updated Typescript Playground

Upvotes: 0

Related Questions