76

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

10 Answers 10

88

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)

Sign up to request clarification or add additional context in comments.

1 Comment

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!
53

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 Comments

That gives me Property 'flat' does not exist on type 'string[][]'. `
@lonix you need to add esnext to libs in tsconfig to make that work
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!
I added an option to use concat, you have to specify the type of bar to be string[] and not string[][]
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! :)
|
20

Here's the simplest option:

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

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

Comments

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.

Comments

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.

Comments

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

Comments

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]]]]]));

Comments

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

Comments

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);
      });

      
    });

Comments

0

const foo = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"],[['x','y','z']]];
const newArray = foo.flat(Infinity)

console.log(newArray)

Array.flat with parameter Infinity means flatten until no nested array exists.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.