Let's say I have these types:
type BaseAnimal = {
species: string
owner: boolean
}
type Cat = BaseAnimal & {
species: 'cat'
hasTail: boolean
}
type Dog = BaseAnimal & {
species: 'dog'
likesWalks: boolean
}
type Animal = Cat | Dog
And I want to create a type called AnimalParams
, which is identical to Animal
except the owner
property, which is a string.
I can't do either of the below.
// This seems to keep the owner property from Animal instead of overwriting
// So it raises an error if I try to specify owner as a string
type AnimalParams = Animal & {
owner: string
}
// This strips away properties unique to Cat or Dog
// So it raises an error if I try to specify hasTail or likesWalks
type AnimalParams = Omit<Animal, 'owner'> & {
owner: string
}
Now, the only workaround I can think of is to do as below, but this seems unnecessarily repetitive. Is there a cleaner, more concise way?
type CatParams = Omit<Cat, 'owner'> & {
owner: string
}
type DogParams = Omit<Dog, 'owner'> & {
owner: string
}
type AnimalParams = CatParams | DogParams
I read a few SO threads on utility types (such as Overriding interface property type defined in Typescript d.ts file, which was for interfaces), but couldn't find what I needed. Thanks for any answers in advance!
Use the Omit utility type to override the type of an interface property, e.g. interface SpecificLocation extends Omit<Location, 'address'> {address: newType} . The Omit utility type constructs a new type by removing the specified keys from the existing type.
An intersection type is a type that merges several kinds into one. This allows you to combine many types to create a single type with all of the properties that you require. An object of this type will have members from all of the types given. The '&' operator is used to create the intersection type.
TypeScript Union Type Narrowing To narrow a variable to a specific type, implement a type guard. Use the typeof operator with the variable name and compare it with the type you expect for the variable.
Instead of manually omitting owner
prop from each type, you can use distributive conditional type:
type OmitOwner<T = Animal> = T extends BaseAnimal ? Omit<T, 'owner'> : never;
type AnimalParams = OmitOwner & {
owner: string
};
Which is equivalent to:
(Omit<Cat, 'owner'> & { owner: string; })
| (Omit<Dog, 'owner'> & { owner: string; })
That's due to automatic distribution over union types
Instantiation of
T extends U ? X : Y
with the type argumentA | B | C
forT
is resolved as(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
Playground
keyof
union produces intersection of keys of types in union, so
type AnimalKeys = keyof Animal // is "species" | "owner"
And implementation of Omit
is:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With