Home > Blockchain >  Unwrapping an as const array into its mutable non-tuple counterpart
Unwrapping an as const array into its mutable non-tuple counterpart

Time:05-24

I have an array with as const:

// just a simple mock class
class Child { _ = "" }

const child = new Child();

const schema = [[child], child] as const; // readonly [readonly [Child], Child];

This array represents a union of types, so here, it would represent an array of Child or another array of Child (one level deeper). In essence, I want to transform the type of schema into (Child | Child[])[].

type T = UnwrapAsConstArray<readonly [readonly [Child], Child]> // => (child | Child[])[]

I'm struggling with the logic to make this transformation possible. This here is my pitiful attempt which does not work that well as you can see at the bottom.

A playground where you can try to make a solution, with some test cases and expected behavior.

Please keep in mind that I would like the solution to be recursive and to work for any amount of nesting.

Similar question: Create a type tuple from an array without "as const". I need the "opposite" which I know is possible.

CodePudding user response:

My approach here would look like:

type UnwrapAsConstArray<T> = 
  T extends readonly any[] ? UnwrapAsConstArray<T[number]>[] :
  { -readonly [K in keyof T]: UnwrapAsConstArray<T[K]> }

This is very similar to a plain DeepMutable<T> type of the form

type DeepMutable<T> = { -readonly [K in keyof T]: DeepMutable<T[K]> }

which will leave primitives unchanged, and recursively strip readonly from any object types, leaving arrays as arrays and tuples as tuples. The only difference is the check for readonly any[], where we explicitly grab the union of its element types T[number], perform UnwrapAsConstArray on that, and produce a regular array.

That passes all the tests in your example:

type UnwrappedSchema = UnwrapAsConstArray<typeof schema>;
//   ^? type UnwrappedSchema = ({ _: string; } | { _: string; }[])[]
// not written as `(Child | Child[])[]`, but structrually identical

type Test01 = Assert<Equals<
  UnwrappedSchema, (Child | Child[])[]>>; // okay

type Test02 = Assert<Equals<UnwrapAsConstArray<
  readonly [readonly [1], 2]>, (2 | 1[])[] // okay
>>;

type Test03 = Assert<Equals<UnwrapAsConstArray<
  readonly [1, readonly [2, 3], readonly [readonly [4, 5, 6]]]>, 
  (1 | (2 | 3)[] | (4 | 5 | 6)[][])[] // okay
>>;

type Test04 = Assert<Equals<UnwrapAsConstArray<
  readonly []>, never[] // okay
>>;

type Test05 = Assert<Equals<UnwrapAsConstArray<
  readonly [1]>, 1[] // okay
>>;

Note well that this will recursively strip read-onliness from any object types like Child you put in there, and it doesn't know when to stop. Anything that doesn't survive a type mapping will be weird (function types will become just {}). If you need to adjust for those cases, you'll have to add logic to do so.

Playground link to code

  • Related