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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With