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[]