I have the following categories of Pet
:
type Pet = 'dog' | 'cat'
Then I have types for allowed DogName
s and CatName
s respectively:
type DogName = 'Jack' | 'Fenton' type CatName = 'Priscilla' | 'Kittykat'
I want to type the following object so that only cat names are allowed under cat and dog names are only allowed under dog, but I also don't want new pet categories to be invented unless they are in the type:
type Pet = 'dog' | 'cat'
type DogName = 'Jack' | 'Fenton'
type CatName = 'Priscilla' | 'Kittykat'
type Pets = {
["dog" in Pet]: {[key in DogName]: boolean}
["cat" in Pet]: {[key in CatName]: boolean}
}
const pets = {
dog: {
Jack: true
},
cat: {
KittyKat: true
}
}
The above does not work, specifically the part of "dog" in Pet
CodePudding user response:
Use, for the pet type, the same mapped structure you are using for the names, and get the associated names with a conditional type.
As suggested by caTS, consider changing the conditional to a lookup table for performance and maintainability in the future when there are more members of the union.
type Pet = 'dog' | 'cat'
type DogName = 'Jack' | 'Fenton'
type CatName = 'Priscilla' | 'Kittykat'
type NameFromPetType<PetType extends Pet> = PetType extends 'dog' ? DogName : CatName;
type Pets = {
[PetType in Pet]: {[key in NameFromPetType<PetType>]?: boolean}
}
CodePudding user response:
The first thing you want to do is tell TypeScript that pets
should be of type Pets
. :-)
Then the pedestrian way to do this is to use Partial<Record<___, boolean>>
:
type Pets = {
dog: Partial<Record<DogName, boolean>>;
cat: Partial<Record<CatName, boolean>>;
};
Playground link (I made the capitalization of KittyKat
consistent)
Obviously, you'll need to add to Pets
if you add to Pet
, but that'll be the case in any solution.