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.