1

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

atlantis
  • 817
  • 1
  • 10
  • 16
  • 1
    What do you mean by "handle" the scenario? The property is of type `string & number` which is [the `never` type](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type), which, by definition, you can never get a value of. A value of both type `A` *and* type `B` would have to have *both* a `string` *and* a `number` value for its property, which is not possible. – jcalz Jul 08 '22 at 21:26
  • *"Is there a way to check the resulting data type?"* yes, you can use an `Expand` utility type to see the fully computed type. See [this playground](https://tsplay.dev/m3PG2W). – Tobias S. Jul 08 '22 at 21:37
  • Do you maybe need a utility type which lets you intersect conflicting type definitions without creating any `never` types? – Tobias S. Jul 08 '22 at 21:39
  • @TobiasS. Thanks for the pointer to Expand utility type. I wasn't aware of that, very helpful! – atlantis Jul 08 '22 at 22:02
  • @TobiasS. How would the utility class handle intersection while avoiding conflicts? A sample? – atlantis Jul 08 '22 at 22:06
  • @jcalz: I meant that `myProperty` should either be number or string, depending on how Typescript types operate, or if there is a way to tell Typescript whether the first or the second operand to '&' takes precedence when there is a conflict. But since the resulting type is 'never', I don't know how to assign a particular value (string or number) to it. – atlantis Jul 08 '22 at 22:07
  • 1
    So then you don't want an intersection, but some other type operation which combines two object types in some other way. Maybe this is a duplicate of https://stackoverflow.com/questions/49682569/typescript-merge-object-types ? – jcalz Jul 08 '22 at 22:43
  • @jcalz: That link is very useful. The simple [one-liner answer](https://stackoverflow.com/a/65814101/174926) is what I am after. Thanks! – atlantis Jul 09 '22 at 01:21

1 Answers1

3

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

Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • Thanks ... your second snippet for the union addresses my use case. Btw, typescriptlang's `// ^?` is very cool and handy. Learnt that from your samples! – atlantis Jul 09 '22 at 01:28