1

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
}
4
  • Can you include what you expect Zoo to look like structurally? Like, if you defined it manually, what would its property keys and value types be? Commented May 9, 2019 at 17:47
  • 2
    By the way, you're not going to get programmatically renamed keys, so any renaming is going to be somewhat manual (e.g., require a type like interface RenameConflictingKeys {age: "personAge"}). Commented May 9, 2019 at 18:21
  • why not just use discriminated union? age: number | string; Commented May 9, 2019 at 20:00
  • @jcalz just edited my question Commented May 10, 2019 at 13:28

1 Answer 1

5

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Hey, I got a question after I read your answer. You said " intersection is only correct for non-conflicting properties", but I got no error with const zoo = <Person & Pet>{ ...pet, ...person }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.