'generic type to add missing properties in type

I have the onMessage function that gets events from SDK of the third party in my app

interface TypeA {
    a:string,
    b:undefined
}

interface TypeB{
   b: 'a'|'b',
   c: 'a'|'b'
}

type TypeC = TypeA | TypeB

const onMassege=( event: TypeC )=>{
   if (event.a === 'a'){
       /*
       TS-ERROR 
       Property 'a' does not exist on type 'TypeC'
        .Property 'a' does not exist on type 'TypeB
        */
  }
  if ( event.b === 'a'){// good
  }
}

in the above example access to properties event.a get error from type-script but event.b does did not get any error because b properties define in all types of TypeC interface

I need a generic type to add missing properties in all types and add c: undefined to my TypeC interface

TS-playGround



Solution 1:[1]

You can create the type you're looking for by defining a generic type KeyUnion that returns the keys of the objects in a union:

type KeyUnion<T> = T extends unknown ? keyof T : never

and use this to create an object type that has a property for each of those keys with as its value type the union of possible types that the property can have in the objects in the union, or undefined:

type Merge<T> = {
  [K in KeyUnion<T>]: T extends Record<K,any> ? T[K] | undefined : never
}

Now Merge<TypeC> will evaluate to

{ a: string | undefined; b: "a" | "b" | undefined; c: "a" | "b" | undefined }

and you can make onMessage type check by using event: Merge<TypeC>.

A downside of this approach is that you lose type safety in the then clauses, as event will always have type Merge<TypeC>, so even in the first case event will still have a c property, and in the second case it will have an a. For this reason it might be safer to just add an extra in check in the condition where necessary, so the type of event will get narrowed down properly in each case:

const onMessage = (event: TypeC) => {
  if ('a' in event && event.a ==='a') {
    // event has type `TypeA` here
  }

  if (event.b ==='a') {
    // event has type `TypeB` here
  }
}

TypeScript playground

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Oblosys