1

I have an object with fixed keys that I use as an enum-like object. The values are variable length arrays. This works as desired. However it requires duplicating the same type assertion on every value in the object. Is there a way to type them all at once instead of individually?

const Direction = 'u' | 'r' | 'd' | 'l'

const gestures = {
  newSubThought: ['r', 'd', 'r'] as Direction[],
  newThought: ['r', 'd'] as Direction[],
  clearThought: ['l', 'r'] as Direction[],
  ...
}

If I omit as Direction[] then the values are too wide, not allowing me to pass gestures.newThought as a Direction[].

The type 'string' is not assignable to type 'Direction'.

If I widen the keys to { [key: string]: Direction[] } then I lose the narrow keys and the autocompletion that comes with it.

2
  • "then it is too narrow" should probably be "then it is too wide", since string is wider than Direction, and string[] is wider than Direction[] (you can assign a Direction[] value to a variable of type string[] but not vice versa) Commented Jun 13, 2021 at 20:50
  • You're right, I must have been thinking of a version with as const which creates narrow values. Updated. Commented Jun 13, 2021 at 21:42

2 Answers 2

1

There is an open issue at microsoft/TypeScript#25214 requesting the ability to annotate the type of the values of an object, while allowing the compiler to somehow infer the keys. For now there is no direct support for this; and the issue is marked as "needs proposal", meaning that it's not clear how such a feature would work. If you're really interested in seeing this happen, you could give that issue a 👍 and possibly suggest how it would work.

For now, though, you can get indirect support for this via a generic identity helper function:

const asGestures = <K extends PropertyKey>(
    gestures: { [P in K]: Direction[] }) => gestures;

Instead of annotating or asserting that each property of your object literal is a Direction[], you call asGestures() on the "plain" object literal. The compiler will constrain the type of the object to something whose values are of type Direction[], and whose keys are of type a key-like type K it infers:

const gestures = asGestures({
    newSubThought: ['r', 'd', 'r'],
    newThought: ['r', 'd'],
    clearThought: ['l', 'r']
    //...
})
/* const gestures: {
    newSubThought: Direction[];
    newThought: Direction[];
    clearThought: Direction[];
} */

Even better, if you make a mistake in your array, the compiler will notice and catch it for you:

const badGestures = asGestures({
    begoneThought: ['r', 'u', 'd', 'e'], // error!
    // --------------------------> ~~~
    // Type '"e"' is not assignable to type 'Direction'.
})

whereas type assertions using as will tend to suppress such errors (one major use of type assertions is to narrow types past what the compiler can verify):

['r', 'u', 'd', 'e'] as Direction[] // no error

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

0

The best solution I could find was using mapped types:

// define an enum-like object with the exact values
// (optionally add `as const` for readonly keys)
const gestureEnum = {
  newSubThought: ['r', 'd', 'r'],
  newThought: ['r', 'd'],
  clearThought: ['l', 'r'],
  ...
}

// widen the type of the gestureEnum values to Direction[] using a mapped type
export const gestures = gestureEnum as {
  [key in keyof typeof gestureEnum]: Direction[]
}

/* The final type:

const gestures: {
  newSubThought: Direction[],
  newThought: Direction[],
  clearThought: Direction[],
  ...
}

*/

Comments

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.