73

I want to flatten string[][] into string[].

The advice given in dozens of SO answers is: [].concat(...arrays).

But that gives me this error:

Argument of type 'string[]' is not assignable to parameter of type 'ConcatArray'.
Types of property 'slice' are incompatible.
Type '(start?: number | undefined, end?: number | undefined) => string[]' is not assignable to type '(start?: number | undefined, end?: number | undefined) => never[]'.
Type 'string[]' is not assignable to type 'never[]'.
Type 'string' is not assignable to type 'never'.

Another way I tried is this:

let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];
let bar = [].concat(...foo);

Which gives a similar error:

Argument of type 'string[]' is not assignable to parameter of type 'ConcatArray'.

Why does it work for everyone but me?

0

9 Answers 9

82

Try this:

const a = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]]
const result = a.reduce((accumulator, value) => accumulator.concat(value), []);
console.log(result)

1
  • 1
    This works without using the esnext library. Works for me on es2017. Best option for those who want to target wider range of browsers. Thanks!
    – lonix
    Commented Jun 11, 2019 at 13:41
50

You can flatten the array with flat()

let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];
let bar = foo.flat()

log

console.log(bar)   // a,b,c,a,b,c,a,b,c 

UPDATE

By correcting the type to string[] you can also use concat

let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];
let bar : string[] = []
bar = bar.concat(foo[0], foo[1], foo[2])
7
  • 9
    That gives me Property 'flat' does not exist on type 'string[][]'. `
    – lonix
    Commented Jun 11, 2019 at 13:17
  • 5
    @lonix you need to add esnext to libs in tsconfig to make that work
    – baao
    Commented Jun 11, 2019 at 13:20
  • 1
    Oh okay now that makes sense :-) But I excluded that for a reason, I'm not using anything bleeding edge till it's current. Would be nice to have that feature right now though!
    – lonix
    Commented Jun 11, 2019 at 13:27
  • I added an option to use concat, you have to specify the type of bar to be string[] and not string[][]
    – Kokodoko
    Commented Jun 11, 2019 at 13:28
  • I think this is the best answer for those using the latest features, unfortunately for me I'll need to use the old for loop! :)
    – lonix
    Commented Jun 11, 2019 at 13:29
20

Here's the simplest option:

let bar = ([] as string[]).concat(...foo);

Like @Kokodoko's approach but with the typings inline.

18

Here's a generic solution:

function flatten<T>(arr: T[][]): T[] {
  return ([] as T[]).concat(...arr);
}

And a deep/recursive extension:

type NestedArray<T> = Array<NestedArray<T> | T>;

function flattenDeep<T>(input: NestedArray<T>): T[] {
  return flatten(input.map(x => Array.isArray(x) ? flattenDeep(x) : [x]));
};

This answer may be more efficient for a deep flatten, I just had fun trying to write something that felt more elegant to me.

0
10

.flat() will also give the type error. You can use Generics to solve this

let st : string[][] | Array<string> = [['a'] , ['b']]
    let bar = [].concat(...st);
    console.log(bar)

Either way, your call. Just know that your type declaration is not right.

6

The code

const res = [].concat(...foo);

should work. I guess it's a misconfiguration in tsconfig that causes that error for you. Make sure that there is at least es2015 (better es2018) in your tsconfig's lib array. To make the new flat work as shown by kokodoko, make sure to also add esnext

"lib": [
  "es2018",
  "dom",
  "esnext"
]
0
5

I believe you have strictNullCheck: true

An empty array with no contextual type ([] in [].concat(arg)) is inferred as never[] under strictNullChecks. and never is not assignable from any other type.

([] as any[]).concat(foo); should do the trick

5

For much more deeply nested array of arrays such as: [1, 2, 3, [4, [5, [6, [7]]]]]

type NestedArray<T> = Array<NestedArray<T> | T>;

const flatten = <T>(input: NestedArray<T>, acc: T[] = []): T[] => {
  return input.reduce((_: T[], current) => {
    if (Array.isArray(current)) return flatten(current, acc);  
    acc.push(current);
    return acc;
  }, []);
};

Usage:

console.log(flatten([1, 2, 3, [4, [5, [6, [7]]]]]));
0

The answer from Steven worked for my jest tests but not in my actual angular 15 app due to a constraint error I can't figure out. Slightly modified as a regular recursive function to keep Typescript quiet :

import { Injectable} from '@angular/core';

export type NestedArray<T> = Array<NestedArray<T> | T>;

export class ArrayHelper {
 
  public static flatten = <T>(input: NestedArray<T>, res: T[] = []): T[] => {
    input.forEach((el: T | NestedArray<T>) => {
      if (Array.isArray(el)) {
        ArrayHelper.flatten(el, res);
      } else {
        res.push(el);
      }
    });

    return res;
  };
}

Example tests:


 describe('flattening', () => {
      it('pass through', () => {
        expect(ArrayHelper.flatten(['hi', 'there'])).toHaveLength(2);
      });

      it('2 level', () => {
        expect(ArrayHelper.flatten(['hi', 'there', ['that', 'is', 'all', 'folks']])).toHaveLength(6);
      });

      it('as per SO', () => {
        expect(ArrayHelper.flatten([1, 2, 3, [4, [5, [6, [7]]]]])).toHaveLength(7);
      });

      
    });

Not the answer you're looking for? Browse other questions tagged or ask your own question.