Home > Mobile >  How to achieve type safety in this example using a generic type?
How to achieve type safety in this example using a generic type?

Time:04-11

Consider the following example:

type A = {
  a: string;
  name: string;
}

type B = {
  b: string;
  name: string
}


function update<T extends A | B>(aOrB: T): T {
  return {...aOrB, name: "updated"}
}

const updatedA = update<A>({a: "a", name: "a"})
const updatedB = update<B>({b: "b", name: "B"})

How can we change this so that TypeScript can catch type errors within the update function?

Example type errors below that aren't being caught:

function update<T extends A | B>(aOrB: T): T {
  return {...aOrB, b: 2, name: "updated"}
}
function update<T extends A | B>(aOrB: T): T {
  return {...aOrB, foo: 2, name: "updated"}
}

I would expect that in each of the examples above that there would be a type error. In the first example, b is not guaranteed to exist, and if it does exist, it's a string, not a number. In the second, foo is not a field on either type.

CodePudding user response:

Instead of a generic type in the returned value, you can achieve it with a returned type A|B

Basically, a generic type cannot determine which type you want to have in the returned result, so you need to set a certain type for it

type A = {
  a: string;
  name: string;
}

type B = {
  b: string;
  name: string
}


function update<T extends A | B>(aOrB: T): (A|B) {
   return {...aOrB, b: 2, name: "updated"} //error
}

const updatedA = update<A>({a: "a", name: "a"})
const updatedB = update<B>({b: "b", name: "B"})

Playground

Another way we can try is having hasOwnProperty to check property in the data, and then using them to cast to a proper type. It's kind of messy, but we would know types beforehand.

type A = {
  a: string;
  name: string;
}

type B = {
  b: string;
  name: string
}


function update<T extends A | B>(aOrB: T): (A|B) {
   if(aOrB.hasOwnProperty("b")) {
     return {...aOrB, a: "new", name: "updated"} as B //error
   }
   return {...aOrB, a: "new", name: "updated"} as A
}

const updatedA = update<A>({a: "a", name: "a"})
const updatedB = update<B>({b: "b", name: "B"})

Playground

  • Related