Home > Net >  typescript: how to infer array of a union instead of union of arrays
typescript: how to infer array of a union instead of union of arrays

Time:12-25

let's say we have a function toArray that takes an argument and returns it as is if it is an array and if not returns an array with the argument value:

// function that takes a value and returns the value if it is of type array and if not returns the value in an array
export const toArray = <T>(value: T | T[]): T extends undefined ? [] : Array<T> => {
  if (typeof value === "undefined") return [] as any;
  return (Array.isArray(value) ? value : [value]) as any;
};

this function works fine as long as the type of the arguments is not union:

number => number[]
number[] => number[]

for unions I got not the expected result:

// wanted
'a'|'b' => ('a'|'b')[]

// currently
'a'|'b' => 'a'[] | 'b'[]

How do I tell typescript to infer array of union instead of union of arrays?

CodePudding user response:

As Typescript documentation says :

To avoid that behavior (distributivity), you can surround each side of the extends keyword with square brackets.

You should define the returned type of the function like this :

[T] extends [undefined] ? [] : Array<T>

CodePudding user response:

Note that TypeScript does not infer literal values from objects (including arrays). For that, you'll need to use a const assertion on object literal input values. (See examples in the code below.)

You can use generics with a function overload signature to achieve your goal.

Here's an example demonstrating the details in your question:

TS Playground

const intoArray: {
  (value?: undefined): unknown[];
  <T extends readonly unknown[]>(value: T): T[number][];
  <T>(value: T): T[];
} = (value?: unknown) => typeof value === "undefined" ? []
  : Array.isArray(value) ? [...value]
  : [value];

const result1 = intoArray();
    //^? const result1: unknown[]

const result2 = intoArray(undefined);
    //^? const result2: unknown[]

const result3 = intoArray('hello');
    //^? const result3: string[]

const result3Literal = intoArray('hello' as const);
    //^? const result3Literal: "hello"[]

const result4 = intoArray(['hello']);
    //^? const result4: string[]

const result4Literal = intoArray(['hello'] as const);
    //^? const result4Literal: "hello"[]

const result5 = intoArray(['a', 'b']);
    //^? const result5: string[]

const result5Literal = intoArray(['a', 'b'] as const);
    //^? const result5Literal: ("a" | "b")[]


I used a function expression above because that's what you showed in the question, but it's more common to use a declaration with the overload pattern (you'll find this in the overwhelming majority of examples online):

TS Playground

function intoArray (value?: undefined): unknown[];
function intoArray <T extends readonly unknown[]>(value: T): T[number][];
function intoArray <T>(value: T): T[];
function intoArray (value?: unknown) {
  return typeof value === "undefined" ? []
    : Array.isArray(value) ? [...value]
    : [value];
}

  • Related