2

I'm looking to generate a type from an Object's keys, and values of arrays of strings. The type needs to represent all the possible strings, i.e.

const Actions = {
  foo: ['bar', 'baz'],
}

# type generated from Actions to equal:
type ActionsType = 'foo' | 'bar' | 'baz'

I need to retain Actions as it's to be passed to a method, therefore:

const Actions = {
  foo: ['bar', 'baz'],
} as const


type ActionsType = keyof typeof Actions | typeof Actions[keyof typeof Actions][number]

whilst generating the type correctly didn't allow me to pass Actions to a method expecting Record<string, string[]> as the keys and values became readonly.

How can I generate the required type whilst still being able to use Actions as a non-readonly Object?

GuyC
  • 6,494
  • 1
  • 31
  • 44
  • What you have done will work, but you need to use a const assertion on your array.. eg.. `foo: ['bar', 'baz'] as const`, otherwise typescript will see the array as been dynamic. – Keith Nov 13 '20 at 10:16
  • @Keith, unfortunately, the method the object is passed to expects a mutable `string[]` therefore setting `as const` on the object, or the individual arrays generates the following error: "The type 'readonly ["bar", "baz"]' is 'readonly' and cannot be assigned to the mutable type 'string[]'" – GuyC Nov 13 '20 at 10:26
  • Seems like you can use the "older" approach to generate types based on an array by using a function helper: [playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBFCuAHANgUxgXhgHgCo1QA8pUwATCGaAJwEswBzAbQF0A+ACgDoeBDahhABcMXAEpMbGP0EBuAFDzQkWAEFgUWuEpYA3vJgwAZiBAiEKVBwDkAI37WANDDu8AXtbGP5AX2mVlaAV5KABPRHR1TW1ccPQsAGtUUJAjODjUmCitSBgAH3SIzOztJiSUtLCitJLIFiYweABbW1RqFkV5I3gwDRzjUw4ZEQAlVFBqMmwaegZnGcZWNgldH0UTEA5aiDFgwNhePvARbdiIzBd7amtZIA). [source](https://stackoverflow.com/a/54061487/863110) – Mosh Feu Nov 16 '20 at 09:35

1 Answers1

4

Here is a simple solution to revert readonlyness of const assertions / as const:

type Mutable<T> = {-readonly [K in keyof T]: Mutable<T[K]>}
The following will compile with a type assertion and adequate type safety:
type T1 = Mutable<typeof Actions> // { foo: ["bar", "baz"]; }

function foo(a: Record<string, string[]>) {}
foo(Actions as Mutable<typeof Actions>) // works

Be aware, that Actions object might be mutated inside foo, so you better treat it as mutable in the containing module as well. A cleaner alternative would be to change foo signature to receive readonly arrays:

function foo2(a: Record<string, readonly string[]>) {}
foo2(Actions) // works without type assertion

Code demo

bela53
  • 3,040
  • 12
  • 27
  • thanks for your help with this. It led to a follow on question, which if you could provide any help on would be awesome: https://stackoverflow.com/questions/64876213/typescript-generate-a-type-of-string-literals-from-an-objects-keys-and-values-r – GuyC Nov 17 '20 at 13:31