4

flatten takes an array of any data type and produces an array with every nested array flatten.

For example, [{}, 'hello', 2, [3, ['ay'], 'oi oi'] becomes [{}, 'hello', 2, 3, 'ay', 'oi oi'], and [[[[5]]]] becomes [5].

I need an interface that would describe such a function. I started writing it thinking it would be simple, but then I stuck with describing an array of anything but other arrays.

interface Flatten {
  (any[]): ...?
}

Any ideas are welcome, thank you :))

2 Answers 2

2

It is doable in TS 4.5 (nightly version) but not in a way you expect.

Thanks to variadic-tuple-types you can do this:


type Reducer<
  Arr extends Array<unknown>,
  Result extends Array<unknown> = []
  > =
  (Arr extends []
    ? Result
    : (Arr extends [infer H, ...infer Tail]
      ? (H extends Array<any>
        ? Reducer<[...H, ...Tail], Result> : Reducer<Tail, [...Result, H]>) : never
    )
  )

// [1,2,3]
type Result = Reducer<[[[1], [[[[[[[2]]]]]]]], 3]> 

// [1, 2, 3, 4, 5, 6]
type Result2 = Reducer<[[[[[[[[[1]]]]]]]],[[[[[[2,3,4]]]],[[[[5,6]]]]]]]> 

How do I use that as a function return value type?

In order to use it with function, you need to convert argument to immutable array:

type Reducer<
  Arr,
  Result extends ReadonlyArray<unknown> = []
  > = Arr extends ReadonlyArray<unknown> ?
  (Arr extends readonly []
    ? Result
    : (Arr extends readonly [infer H, ...infer Tail]
      ? (H extends ReadonlyArray<any>
        ? Reducer<readonly [...H, ...Tail], Result> : Reducer<Tail, readonly [...Result, H]>) : never
    )
  ) : never


const flatten = <
  Elem,
  T extends ReadonlyArray<T | Elem>
>(arr: readonly [...T]): Reducer<T> =>
  arr.reduce((acc, elem) =>
    Array.isArray(elem)
      ? flatten(elem) as Reducer<T>
      : [...acc, elem] as Reducer<T>,
    [] as Reducer<T>
  )


const result = flatten([[[[[[1]]], 2], 3]] as const)

Playground

You should have also add second argument to reduce method.

More explanation you can find in my article.

If you want to better understand how Reducer utility type work, see this exmaple:

const Reducer = <T,>(Arr: ReadonlyArray<T>, Result: ReadonlyArray<T> = [])
  : ReadonlyArray<T> => {

  if (Arr.length === 0) {
    return Result
  }
  const [Head, ...Tail] = Arr;

  if (Array.isArray(Head)) {
    return Reducer([...Head, ...Tail], Result)
  }

  return Reducer(Tail, [...Result, Head])
}
Sign up to request clarification or add additional context in comments.

4 Comments

That looks really complicated to me. How do I use that as a function return value type?
I will ad more explanation later
Result is not working
@PYTHONDEVELOPER999 please see updated code
0

If you know all the data types flat array should return, you may define it like below:

type Flat = string|number|FlatObj;
type FlatArr = Array<Flat>;
type FlatObj = {[key: string]: Flat|FlatArr};

In your example you had nested empty object, so I assume you want it to contain only flat arrays too, FlatObj is definition of it.

Now if you try to assign nested arrays in any FlatArr variable, it should complain about it.

Link to playground

If you are looking for a Type Guard, here one example for FlatArr and FlatObj:

type AnyObj = {[key: string]: any};

function isFlatObj(obj: AnyObj): obj is FlatObj {
  let flat = true;
  for (const key in obj) {
    if (Array.isArray(obj[key]) && !isFlatArr(obj[key])) flat = false;
    else if (typeof obj[key] === 'object' && !isFlatObj(obj[key])) flat = false;
  }
  return flat;
}

function isFlatArr(arr: any[]): arr is FlatArr {
  let flat = true;
  arr.map((item) => {
    if (Array.isArray(item)) flat = false;
    else if (typeof item === 'object' && !isFlatObj(item)) flat = false;
  });
  return flat;
}

To make FlatArr, you can define a function for it. I made two functions below. One makes Arrays flat, the other makes sure object arrays are flat too:

const flattenArr = (arr: any[]): FlatArr => {
  const flatArr: FlatArr = [];
  for (const item of arr) {
    if (Array.isArray(item)) flatArr.push(...flattenArr(item));
    else if (typeof item === 'string') flatArr.push(item);
    else if (typeof item === 'number') flatArr.push(item);
    else if (typeof item === 'object') flatArr.push(flattenObj(item));
  }
  return flatArr;
}

const flattenObj = (obj: AnyObj): FlatObj => {
  let flatObj: FlatObj = {};
  for (const key in obj) {
    if (Array.isArray(obj[key])) flatObj[key] = flattenArr(obj[key]);
    else if (typeof obj[key] === 'string') flatObj[key] = obj[key];
    else if (typeof obj[key] === 'number') flatObj[key] = obj[key];
    else if (typeof obj[key] === 'object') flatObj[key] = flattenObj(obj[key]);
  }
  return flatObj;
}

3 Comments

It's not working. Typescript doesn't warn me when I simply return the same array
Maybe rename FlatArray to FlatArr, since the FlatArray might be defined somewhere. I tested the code with this change and TypeScript (4.4.2) does warn me about any nested arrays when variable type is FlatArr which is the return type of the flatten function in the above example.
Check this. TS doesn't require type guarding.

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.