I have the following data:
const pets = [
{
type: 'cat',
name: 'penny'
},
{
type: 'dog',
name: 'Fido'
},
{
type: 'fish',
name: 'Nemo'
}
];
A pet should only be considered valid if it's in either one of those 3 exact shapes (i.e. the key AND the value must match). For example this is a valid pet:
{
type: 'dog',
name: 'Fido'
}
These are not:
{
age: 'dog',
name: 'Fido'
},
and
{
type: 'dog',
name: 'Sammy'
},
etc..
My actual object is much larger than this so I don't want to manually define this type. How can it be done dynamically?
I tried this but to no avail:
type Pet = {
[key in keyof typeof pets]: typeof pets[key];
};
CodePudding user response:
You need to use an as const
assertion on pets
to tell typescript to infer the most specific types that it can. Other wise pets
is just typed as { type: string, name: string }[]
, which isn't helpful.
const pets = [
{
type: 'cat',
name: 'penny'
},
{
type: 'dog',
name: 'Fido'
},
{
type: 'fish',
name: 'Nemo'
}
] as const;
Now pets
is typed as:
const pets: readonly [{
readonly type: "cat";
readonly name: "penny";
}, {
readonly type: "dog";
readonly name: "Fido";
}, {
readonly type: "fish";
readonly name: "Nemo";
}]
Note how there are three specific objects with different specific strings.
Now the Pet
type is simply:
type Pet = typeof pets[number]
/*
{
readonly type: "cat";
readonly name: "penny";
} | {
readonly type: "dog";
readonly name: "Fido";
} | {
readonly type: "fish";
readonly name: "Nemo";
}
*/
Indexing an array type by number
gets a union of all array member types. In this case, each specific type of pet.
And now this works as you expect:
const fido: Pet = { // works
type: 'dog',
name: 'Fido'
}
const bad1: Pet = { // error because Nemo is a fish
type: 'cat',
name: 'Nemo'
}
const bad2: Pet = { // error because penny is a cat
type: 'dog',
name: 'penny'
}
CodePudding user response:
You can also use Record
with an enum
to fix the types with certain values. I don't know if that is exactly what you want but you can try.
// Define the keys here they will make sure that nothing
// other then these keys exist here.
enum Keys {
TYPE = "type",
NAME = "name",
}
// Create a Record type that can only take this key but any string value
type Pet = Record<Keys, string>;
const pets: Pet[] = [
{
type: "cat",
name: "penny",
},
{
type: "dog",
name: "Fido",
},
{
type: "fish",
name: "Nemo",
},
];
// Valid
const pet: Pet = {
type: "cat",
name: "fido",
};
// Invalid
const invalidPet: Pet = {
age: 'dog',
name: 'Fido'
};
Again note that Keys are what define what you can take as keys, whereas the values are not restricted and they can be anything.
If you want to restrict values too (not a valid case because it doesn't make any sense), you can create an enum
for that as well.