Home > Net >  Use an array of readonly property keys as TypeScript union
Use an array of readonly property keys as TypeScript union

Time:12-10

I want to declare arrays at the root of the module that are an array of keys that correspond to a model, but can be used in various parts of the app as unions.

const editableFields: (keyof Car)[] = ["name", "description"] as const

This generates the error:

The type 'readonly ["name", "description"]' is 'readonly' and cannot be assigned to the mutable type '(keyof Car)[]'.ts(4104)

I want to be able to create an union from this array, like "name" | "description" so that I can use it in various parts of the app, as well as somehow pass it to lodash pick and omit functions:

lodash.pick(carInstance, ...editableFields)

If I remove the (keyof Car)[] it all starts working:

const editableFields: readonly ["name", "description"]

But then I lose the type safety!

Would love to hear if anyone has set this up successfully. Thanks!

CodePudding user response:

When you use a const assertion, any array literal will get a readonly tuple type, which is a "read-only" array, but you were trying to assign that to a mutable or read-write array type, and the compiler doesn't see those as compatible.

One way to fix this is to just annotate the variable as readonly (keyof Car)[] instead of (keyof Car)[] so that the readonly conflict goes away:

const editableFields1: readonly (keyof Car)[] = ["name", "description"] as const
// const editableFields1: readonly (keyof Car)[]

Another approach is to use the new satisfies operator instead of a const assertion to tell the compiler that you want to be sure that the type of editableFields2 is assignable to (keyof Car)[]. This gives the compiler a context in which to interpret ["name", "description"], so that it doesn't get widened to string[]:

const editableFields2 = ["name", "description"] satisfies (keyof Car)[];
// const editableFields2: ("name" | "description")[]

If you made a mistake with one of your fields, then the compiler would output a warning (which is presumably what you mean by losing type safety when you don't annotate).


Finally, you could combine these approaches and use a const assertion with the right readonly type, but still use the satisfies operator so that the compiler checks the type without widening it:

const editableFields3 = ["name", "description"] as const satisfies readonly (keyof Car)[];
// const editableFields3: readonly ["name", "description"]

Here the type of editableFields3 is as specific as possible; the compiler knows that the first element is "name" and the type is "description".

Playground link to code

  • Related