Home > OS >  How to create types array types from unions
How to create types array types from unions

Time:04-06

This question is kind of hard to express for me which might be the reason why I did not find anything on researching.

I'm trying to get a type that ends up with having arrays only. I always want to end up with "one-level" arrays (If arrays of arrays are given, I don't care about the output. This does not happen in my case). So the wanted outcome is the following:

type T1 = ForceArray<string> // wanted: string[]

type T2 = ForceArray<string[]> // wanted: string[]

I can do this with the following code:

type ForceArray<T> = NonNullable<T> extends Array<any> ? T : Array<T>

NonNullable is used here because in the actual use case, ForceArray<T> is used on interface properties which can be optional. This works well, except for union types.

type T3 = ForceArray<string | string[]> // shows (string | string[])[] but I want string[]

Also other union types should be converted to arrays which does not work either:

type T4 = ForceArray<number | string[]> // wanted: number[] | string[]

type T5 = ForceArray<number[] | string> // wanted: number[] | string[]

In the typescript docs they kind of relate to this by talking about Distributive conditional types. But as far as I understand the docs, my code should just work. So obviously I didn't understand the docs correctly.

Is there a way to get the expected behaviour?


Additional information:

The actual code that produces the output is extremely simple which is the reason why I beleave there should be a solution:

const forceArray = (value) => Array.isArray(value) ? value : [value];

This function is used in a context like this, where obj can implement any interface (with optional properties):

const convertObjectValuesToArray = (obj) => {
    const newObj = {};
    for (const key of obj) {
        newObj[key] = forceArray(obj[key]);
    }
    return newObj;
}

CodePudding user response:

The NonNullable seems to be the problem here because it disables the distributivity of the generic type similar to brackets []. We could remove it and sorround the returned types with it instead.

type ToArray<Type> = Type extends Array<any> ? NonNullable<Type> : NonNullable<Type>[]

type A = ToArray<string>              // string[]
type B = ToArray<string[]>            // string[]
type C = ToArray<string | number[]>   // string[] | number[]
type D = ToArray<string | null>       // string[] | never[]

The only problem here is D where the return type is string[] | never[]. But we can wrap the whole thing into an Exclude to get rid of never

type ToArray<Type> = Exclude<Type extends Array<any> ? NonNullable<Type> : NonNullable<Type>[], never[] | never>

type A = ToArray<string>              // string[]
type B = ToArray<string[]>            // string[]
type C = ToArray<string | number[]>   // string[] | number[]
type D = ToArray<string | null>       // string[]
  • Related