Home > Blockchain >  Typescript: Property name collisions in types vs interfaces
Typescript: Property name collisions in types vs interfaces

Time:07-09

Let's say I have 2 types with the same property name but different data types:

type A = {
  myProperty:string
}

type B = {
  myProperty:number
}

If I extend both using an interface, I see an error as expected:

interface C extends A, B {}
Interface 'C' cannot simultaneously extend types 'A' and 'B'.
  Named property 'myProperty' of types 'A' and 'B' are not identical.

But if I use a type to extend both, there is no exception. In this case, what's the resulting data type of myProperty? Is there a way to check the resulting data type?

type C = A & B; // no error

I can't set a value for myProperty (string or number) if C is declared as a type. What's the best way to handle this scenario?

function F(myParameter:C) {
  console.log(myParameter);
}

F({myProperty: 90});
(property) myProperty: never
Type 'number' is not assignable to type 'never'.ts(2322)
index.ts(3, 3): The expected type comes from property 'myProperty' which is declared here on type 'C'

Same error if I call the function as F({myProperty: 'test data'});.

Playground: https://codesandbox.io/s/typescript-playground-export-forked-dglbq5?file=/index.ts

CodePudding user response:

If you want to intersect two conflicting types but still want to instantiate the resulting type, there are multiple options to handle it. Note that both solutions will not resolve conflicts for properties nested deeper than the first level.

Let's say we have two interfaces A and B which we want to intersect.

interface A {
    a: string
    b: string
}

interface B {
    a: number
    c: string
}

The first option could be to omit the conflicting property from the resulting type.

type IntersectByOmitting<A, B> = {
    [K in keyof (A & B) as K extends keyof A & keyof B 
      ? [A[K] & B[K]] extends [never] 
        ? never 
        : K 
      : K
    ]: K extends keyof A ? A[K] : B[K & keyof B]
}

type T0 = IntersectByOmitting<A, B>
//   ^? type T0 = { b: string; c: string; }

Since a resolves to never in the intersection, it will be omitted from the resulting type.


As you said in the comment, you may want to have a union of the conflicting properties instead.

type IntersectByUnion<A, B> = {
    [K in keyof (A & B)]: K extends (keyof A & keyof B) ? 
      [A[K] & B[K]] extends [never] 
        ? A[K] | B[K] 
        : A[K] & B[K]
      : K extends keyof A ? A[K] : B[K & keyof B]
}

type T1 = IntersectByUnion<A, B>
// type T1 = {
//    a: string | number;
//    b: string;
//    c: string;
// }

The type of a is now string | number.

Playground

  • Related