danielrvt
danielrvt

Reputation: 10926

Typescript: Change conflicting property name when extending interface

Is there a way to do something like this with typescript, renaming the property name or something?

interface Person {
    name: string
    age: number
}

interface Pet {
    age: string
}

interface Zoo extends Pet, Person {}

Without getting this error:

Interface 'Zoo' cannot simultaneously extend types 'Person' and 'Pet' Named property 'age' of types 'Person' and 'Pet' are not identical.ts(2320)

[EDIT]

I'm looking for a way to get this result:

interface Zoo {
    name: string
    age: number
}

Upvotes: 1

Views: 3551

Answers (1)

jcalz
jcalz

Reputation: 328758

Ah, so you're not trying to rename properties; just remove any conflicting properties from one of the interfaces.

This is very similar to the idea of an object spread type operator, which TypeScript doesn't currenty have as part of the language at the type level. Currently the type system treats generic spreads as an intersection, which is only correct for non-conflicting properties.) At the value/expression level, TypeScript does properly handle spreads of concrete types, so you could get the particular Zoo you're looking for by convincing the type system that you have a value of type Pet, a value of type Person, and a value formed by spreading them:

interface Person {
  name: string
  age: number
}

interface Pet {
  age: string
  trained: boolean // I added this because otherwise Zoo=Person which is weird
}

declare const person: Person; // pretend we have a person
declare const pet: Pet; // pretend we have a pet
const zoo = { ...pet, ...person }; // spread them into a new variable
type ZooType = typeof zoo; // get the type of that variable
// type ZooType = {
//   name: string;
//   age: number;
//   trained: boolean;
// } 
interface Zoo extends ZooType { }; // make it an interface because why not

If you don't feel like mucking around with values (especially if you don't have any lying around) and you want to do this purely at the type level, you can make a spread-like type operator yourself with mapped and conditional types. One of the language designers suggested an implementation of Spread<L,R> that works well enough with some caveats around optional/readonly/etc properties.

For your case, since you have no optional or readonly properties, here is a simpler implementation, which I'll call Merge<L, R>:

type Merge<L, R> = R & Pick<L, Exclude<keyof L, keyof R>>;

And you can see it behaves similarly to the above value-level spread:

interface Zoo extends Merge<Pet, Person> { };

declare const zoo: Zoo;
zoo.name; // string
zoo.age; // number
zoo.trained; // boolean

Caveats: if age were optional in Person, then Merge<Pet, Person> would make age optional in Zoo. That might be what you want, but a spread wouldn't behave that way; {...pet, ...person} would always have an age since Pet requires one. In a spread then, Zoo["age"] would be something like string | number, which the Spread<L, R> operator linked above would deal with more correctly. Also neither Merge<> nor the linked Spread<> are guaranteed to do "the right thing" with readonly properties, especially since it's not clear to me what that right thing is.

Okay, hope that helps. Good luck!

Upvotes: 5

Related Questions