Reputation: 149
In the following example, I'm able to extract explicit keys from the object, but i'm unable to enforce its values
const myCollection = {
a: {test: 1},
b: {test: 2},
c: {text: 3} // no error
};
type MyCollectionKeys = keyof typeof myCollection;
// MyCollectionKeys is 'a' | 'b' | 'c'
When enforcing the values using the following snippet, you lose explicit keys:
type CollectionValue = {test: number};
const myCollection: ({[k: string]: CollectionValue}) = {
a: {test: 1},
b: {test: 2},
c: {text: 3} // Type '{ text: number; }' is not assignable to type 'CollectionValue'.
};
type MyCollectionKeys = keyof typeof myCollection;
// MyCollectionKeys is string | number
A workaround I found is to proxy the object like so:
type CollectionValue = {test: number};
const _myCollection = {
a: {test: 1},
b: {test: 2},
c: {text: 3}
};
type MyCollectionKeys = keyof typeof _myCollection;
// MyCollectionKeys is 'a' | 'b' | 'c'
const myCollection: ({[k in MyCollectionKeys]: CollectionValue}) = _myCollection;
// Type '{ a: { test: number; }; b: { test: number; }; c: { text: number; }; }' is not assignable to type '{ a: CollectionValue; b: CollectionValue; c: CollectionValue; }'.
// Types of property 'c' are incompatible.
// Property 'test' is missing in type '{ text: number; }' but required in type 'CollectionValue'
The drawbacks of this method are:
Is there a more elegant solution for this?
Upvotes: 1
Views: 458
Reputation: 184516
Unfortunately generics cannot be partially inferred from use, so in this case i would probably just enumerate the valid keys:
const myCollection: Record<'a' | 'b' | 'c', { test: number }> = {
a: {test: 1},
b: {test: 2},
c: {text: 3} // error
};
If you do not mind introducing a pointless function:
const check = <T extends string>(collection: Record<T, { test: number }>) => collection;
const myCollection = check({
a: {test: 1},
b: {test: 2},
c: {text: 3} // error
});
Upvotes: 3