Is it possible to create a generic object type that would force the keys to be used as values internally?
For example, if I want an object to have the id that matches the key I would use something like the code below:
export type ObjectWithId<
T extends string,
E extends Record<string, unknown>
> = {
[K in T]: { id: K } & E;
};
type IdKeys = 'dog' | 'cat';
const animals: ObjectWithId<IdKeys, { title: string }> = {
dog: { id: 'dog', title: 'Some title' },
cat: { id: 'cat', title: 'Another title' },
};
But I wonder if there is a solution that could work without the need to declare the IdKeys
and therefore would infer these values from the object.
e.g.
const animals: ObjectWithId<{ title: string }> = {
dog: { id: 'dog', title: 'Some title' },
cat: { id: 'cat', title: 'Another title' },
};
CodePudding user response:
You need to create extra builder function in order to make this type validation:
const foo = <
Id extends PropertyKey,
Value extends Record<string, unknown>,
>(a: Record<Id, { id: Id } & Value>) => a
// ok
foo({
dog: { id: 'dog', title: 'Some title' },
cat: { id: 'cat', title: 'Another title' },
})
// error
foo({
dog: { id: 'cat', title: 'Some title' },
cat: { id: 'cat', title: 'Another title' },
})
Above approach is good, but it might be even better. As you might have noticed, if id
is wrong, the whole row is highlighted.
Consider this example:
type ErrorMessage<T extends string> = `ID property should be equal to ${T}`
type ValidateId<Obj extends Record<PropertyKey, { id: PropertyKey }>> = {
/**
* Iterate throught eahc Key
*/
[Key in keyof Obj]:
/**
* Check whether [id] property is equal to Key
*/
Obj[Key]['id'] extends Key
/**
* If yes, leave object as it is
*/
? Obj[Key]
/**
* Itherwise, replace [id] value with readable error message
*/
: Omit<Obj[Key], 'id'> & {
id: ErrorMessage<Key & string>
}
}
const foo = <
Id extends PropertyKey,
Value extends Record<string, unknown>,
Data extends Record<Id, { id: Id } & Value>
>(a: ValidateId<Data>) => a
foo({
dog: { id: 'dog2', title: 'Some title' }, // only ID property is highlighted
cat: { id: 'cat', title: 'Another title' },
})
Now, only id
property is highlighted with nice error message.
If you are interested in this approach, you can check my articles: Type inference on function arguments and Validators
P.S. If you don't like using function and still want to achieve what you want, my answer is - no. It is impossible in typescript generate such constraint without knowing upfront your keys