Defining A Choice Of Index Type On An Interface
Solution 1:
Unions like Cat | Dog
are inclusive, meaning that something is a Cat | Dog
if it is a Cat
or a Dog
or both. TypeScript doesn't have a general exclusive union operator. If your unions share a common property with distinct values, you can use discriminated unions like @MateuszKocz suggests. Otherwise, you could build your ownXor
type function for objects:
type ProhibitKeys<K extends keyof any> = { [PinK]?: never }
type Xor<T, U> = (T & ProhibitKeys<Exclude<keyof U, keyof T>>) |
(U & ProhibitKeys<Exclude<keyof T, keyof U>>);
Then you can define Animal
as the exclusive union of Cat
and Dog
, intersected with the additional properties common to all Animal
s:
typeAnimal = Xor<Cat, Dog> & { weight: number };
Now you can get your desired behavior (type annotations are superior to type assertions so I am using those here):
// Success
const dog: Animal = {
weight: 5,
dog: { sound: "woof" }
}
// Error, {lives: number} not assignable to undefined
const errorAnimal: Animal = {
weight: 5,
dog: { sound: "woof" },
cat: { lives: 9 }
}
Hope that helps; good luck!
Solution 2:
If you are willing to change your code a bit, then tagged unions will be the answer you're looking for.
interface CommonAnimal {
weight: number
}
interface Dog extends CommonAnimal {
// This is the important part. `type` a tag used by TS to recognise this type.type: 'dog'
sound: string
}
interface Cat extends CommonAnimal {
type: 'cat'
lives: number
}
type Animal = Dog | Cat
const dog: Animal = {
type: 'dog',
weight: 10,
sound: 'woof'
}
const cat: Animal = {
type: 'cat',
weight: 5,
lives: 9
}
const robot: Animal = {
type: 'robot'// error
}
That way you'll be able to keep values on one level, without nesting, while satisfying TS's type recognition.
Post a Comment for "Defining A Choice Of Index Type On An Interface"