Home > OS >  Is there a way of using an interface type discriminator as a string union?
Is there a way of using an interface type discriminator as a string union?

Time:12-19

So for the following simplified example with a union of two interfaces with a "type" discriminator:

type ABBA = A | B;
interface A { type: 'a', argForA: string }
interface B { type: 'b', argForB: boolean }

I would like to be able to construct an object but limit its keys to the type discriminators - so

const config = {
  a: 123,
  b: 234,
  c: 345, // should error as c is not a type discriminator of ABBA
}

What i'm after is essentially the opposite of the Record<A,B> utility type, but i can't see anything in the utility type documentation that functions this way.

I have tried searching online for an answer, but the answers are clouded by an overwhelming number of tutorials on how to use unions on interfaces.

CodePudding user response:

Consider this:

type ABBA = A | B;

interface A { type: 'a', argForA: string }
interface B { type: 'b', argForB: boolean }

type Values<T> = T[keyof T]

type OnlyDiscriminators = Values<ABBA>

const config: Record<OnlyDiscriminators, number> = {
    a: 123,
    b: 234,
    c: 345, // error
}

enter link description here

Values - obtains a union of common/shared props of A and B. Since discriminator should be shared property, Values returns a | b

Record<OnlyDiscriminators, number> creates a hash map data structures where OnlyDiscriminators represents keys and number represents values.

However, your A and B interfaces might have more props in common.

Consider this example:

type ABBA = A | B;

interface A { type: 'a', argForA: string, id: string }
interface B { type: 'b', argForB: boolean, id: string }

type Values<T> = T[keyof T]
type GetDiscriminators<D extends string, Obj extends Record<D, unknown>> = Values<Pick<Obj, D>>

type OnlyDiscriminators = GetDiscriminators<'type', ABBA>

const config: Record<OnlyDiscriminators, number> = {
    a: 123,
    b: 234,
    c: 345, // error
}

CodePudding user response:

interface A {
  type: 'a',
  a: number;
}

interface B {
  type: 'b',
  b: string;
}

type AORB = A | B;

const x: AORB = {
  type: 'b',
  b: 's'
}

const y: AORB = {
  type: 'a',
  a: 2
}

// Will not compile
const z: AORB = {
  type :'b',
  a: 2
}

// Will not compile
const n: AORB = {
  type :'a',
  b: "2"
}

This looks very similar to your own suggestion, and it works just fine.

  • Related