Oh, so your goal is not to change property names; you just want to eliminate any conflicting properties from one of the interfaces.
This concept closely resembles the notion of an object spread type operator, which TypeScript currently lacks as a part of its language at the type level. Currently, when it comes to generic spreads, the type system treats them as an intersection, which is only accurate for non-conflicting properties. However, at the value/expression level, TypeScript effectively manages spreads of concrete types. As such, you can achieve the specific Zoo
you desire by convincing the type system that you possess a value of type Pet
, another 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; // acquire the type of that variable
interface Zoo extends ZooType { }; // convert it into an interface just because
If you're hesitant about dealing with values (especially if you don't have any readily available) and wish to carry out this process purely at the type level, you can craft a spread-like type operator yourself using mapped and conditional types. A recommended implementation was suggested by one of the language designers for Merge<L,R>
, which works sufficiently well with certain considerations regarding optional/readonly/etc properties.
In your scenario, where there are no optional or readonly properties involved, here is a simpler version known as Merge<L, R>
:
type Merge<L, R> = R & Pick<L, Exclude<keyof L, keyof R>>;
You'll notice that it mirrors the functionality of the aforementioned value-level spread:
interface Zoo extends Merge<Pet, Person> { };
declare const zoo: Zoo;
zoo.name; // string
zoo.age; // number
zoo.trained; // boolean
Please bear in mind that if age
were optional in Person
, then Merge<Pet, Person>
would make age
optional in Zoo
. This outcome may align with your intentions, but a spread wouldn't exhibit the same behavior; {...pet, ...person}
would always include an age
since it is mandatory in Pet
. Consequently, Zoo["age"]
might be something like string | number
, which the previously shared Spread<L, R>
operator could manage more accurately. Furthermore, neither Merge<>
nor the linked Spread<>
guarantee flawless handling of readonly
properties, especially given the ambiguity surrounding the appropriate treatment of such properties.
Well, I hope this information proves helpful! Best of luck!