Typescript: enforce object values without losing explicit keys

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

Answers (1)

brunnerh
brunnerh

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

Related Questions