Spreading an Object Literal and Implementing a Symbol.iterator

Hi. On the sample code below, how to spread one multi-dimensional and one two dimensional array in one single step. Being the arrays inside an Object.

const obj = {
  a1: ["A", "B", "C"],
  a2: [["D", "E", "F"], ["G", "H", "I"]],
};

I can do:

const arr = [obj.a1, ...obj.a2];

But I can’t do:

const arr = [...obj];

Is it possible to make the object iterable in order to get the same results using [...obj] ?
Thanks in advance :slight_smile:

A little more than one line. Got to be a better way.

const
    obj = {
        a1: ["A", "B", "C"],
        a2: [ ["D", "E", "F"], ["G", "H", "I"], [ ["J","K"], ["M","N"] ] ],
        a3: ["O", "P", "Q"]
    },

flattenish = function(obj) {
    
    let result = [];
    
    for (let i = 0, len = obj.length; i < len; i += 1) {

        if (Array.isArray(obj[i])) {

            let arr = flattenish(obj[i])

            if (arr.hasElements) { 
                
                delete arr.hasElements /* remove flag */
                arr = [arr]
            }

            result = result.concat(arr)

        } else {

            //  add hasElements flag to differentiate 
            //  between returning an array of elements 
            //  and an array of multiple arrays

            result.hasElements = true 
            result.push(obj[i])
        }
    }
    return result
}

console.dir(flattenish(Object.values(obj)));

/*
Output:

0: (3) ["A", "B", "C"]
1: (3) ["D", "E", "F"]
2: (3) ["G", "H", "I"]
3: (2) ["J", "K"]
4: (2) ["M", "N"]
5: (3) ["O", "P", "Q"]
length: 6

*/
1 Like

Hey,

As we are going from an array of nested arrays to an array of one dimensional arrays, we can also use Array.prototype.reduce:

const obj = {
  a1: ['A', 'B', 'C'],
  a2: [ ['D', 'E', 'F'], ['G', 'H', 'I'], [ ['J','K'], ['M','N'] ] ],
  a3: ['O', 'P', 'Q']
};

function destructure(arr, src=[]){
  const retVal = arr.reduce((acc, el) => {
    if (el.some(el => Array.isArray(el))) destructure(el, acc);
    else acc.push(el);

    return acc;
  }, src);

  return retVal;
}

const res = destructure(Object.values(obj));
console.dir(res);

Outputs:

[
  [ 'A', 'B', 'C' ],
  [ 'D', 'E', 'F' ],
  [ 'G', 'H', 'I' ],
  [ 'J', 'K' ],
  [ 'M', 'N' ],
  [ 'O', 'P', 'Q' ]
]
2 Likes

Well done James, thanks for posting a much more elegant solution and name ‘destructure’.

edit:
It was well worth running through your code.

The first thing to crop up is that console.log in chrome appears to be asynchronous.

So for instance debugging and changing

if (el.some(el => Array.isArray(el))) destructure(el, acc);

to

if (el.some(el => Array.isArray(el))) { console.log('acc => ', acc); destructure(el, acc); }

will give some confusing results. By the time the first log is outputted the entire code has finished executing and the accumulator shows the final output — not the empty array you would expect.

The better solution is to paste your code into a snippet in chrome, add a breakpoint to function destructure and step through the code. It gives you a very clear picture of what is what.

I have used the initialValue on reduce many times with empty arrays, empty strings etc. but in your code the initialValue has the added factor of being supplied by the recursive calls (src). It hurts my head a bit, but very nifty indeed.

No worries :slight_smile:

Yeah, this has bitten me before. As you say, adding breakpoints is a nice way to see what is going on under the hood.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.