1

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:

  • you must have a proxy object
  • if you violate the type, the error is thrown at the assignment instead of being thrown inside the original object at the offending key

Is there a more elegant solution for this?

0

1 Answer 1

3

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
});
Sign up to request clarification or add additional context in comments.

2 Comments

I'm trying to avoid enumerating the keys myself as it is tedious. I'll give the function a try, as it will only run once at init and it's mostly a noop
the function approach looks nice, and you can even make it generic: export function assertObjectRecordIntegrity<T>() { return <Keys extends string>(object: Record<Keys, T>) => object; }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.