4

I have a nested object and I want to flatten/map it into a single-layered, table-like object.

[{
    a: 1,
    b: 2,
    c: [{
        x: 10,
        y: 20
    }, {
        x: 30,
        y: 40
    }]
}, {
    a: 3,
    b: 4,
    c: [{
        x: 50,
        y: 60
    }, {
        x: 70,
        y: 80
    }]
}]

From that, I want to get something like this:

[{
    a: 1,
    b: 2,
    x: 10,
    y: 20
}, {
    a: 1,
    b: 2,
    x: 30,
    y: 40
}, {
    a: 3,
    b: 4,
    x: 50,
    y: 60
}, {
    a: 3,
    b: 4,
    x: 70,
    y: 80
}]

Sure, I could simply iterate over the object with two for loops and put the result info a separate array, but I wonder, if there is a simpler solution. I already tried to play around with flatMap. It works, if I only want the c portion of my nested object, but I don't know how to map a and b to this object.

As some of you asked for some working code, this should do it (untested):

let result = [];

for (const outer of myObj)
  for (const inner of outer.c)
    result.push({a: outer.a, b: outer.b, x: inner.x, y: inner.y});

The question is, if there is a functional one-liner or even another, better approach. In reality, my object consists of four layers and the nested for loops become messy quite fast.

MFerguson
  • 1,739
  • 9
  • 17
  • 30
André Reichelt
  • 1,484
  • 18
  • 52
  • This might better be suited for https://codereview.stackexchange.com/ if you actually don't have any problem – Brewal Mar 11 '20 at 13:32
  • 1
    Your not really flattering here, because you would have duplicate x & y's if it was. Also is this structure always this way, what if the `{x,y}` had another sub array, would this multiple out even more. – Keith Mar 11 '20 at 13:33
  • 1
    @Brewal Definitely not a question for codereview. – Teemu Mar 11 '20 at 13:33
  • @Brewal In my actual code, there are not just two layers, but four. If I try to use the `for` loop method there, it gets pretty ugly pretty quickly. – André Reichelt Mar 11 '20 at 13:33
  • @Teemu well, that's not a question for SO either. If the code was provided, I think code review is a good place... – Brewal Mar 11 '20 at 13:35
  • @Keith The base object is generated by a c# web api method and, in reality, contains a list of picking lists with pick lines and storage locations. The structure is as fixed as the c# class. – André Reichelt Mar 11 '20 at 13:36
  • If this has even more levels, then one idea is to pass what level you want to flatten too.. eg... `flatter(array, 4)` could then flatten down to the 4th level, this could easily be make functionally if that was the case. – Keith Mar 11 '20 at 13:36
  • Take a look at [this question](https://stackoverflow.com/questions/11922383/how-can-i-access-and-process-nested-objects-arrays-or-json), notice especially parts introducing recursive approach. – Teemu Mar 11 '20 at 13:36
  • 1
    @Brewal But they're fixing existing and working code at codereview, when not having code at all is even harder to answer there than here. – Teemu Mar 11 '20 at 13:37
  • @Brewal If it'll make you happy, I could provide some dummy code. As this is just some garbage exampe data, I haven't written any method for that yet. I'll edit my question. – André Reichelt Mar 11 '20 at 13:39
  • @AndréReichelt Maybe if you could provide at least some attempt, we could answer your question without just giving you a solution ? It's definitely better if you show you tried something :) – Brewal Mar 11 '20 at 13:41
  • 1
    @Teemu I assumed there was actually already a working solution. – Brewal Mar 11 '20 at 13:48
  • What do the other nested levels look like? Can you include that in the question? – M0nst3R Mar 11 '20 at 13:56
  • @GhassenLouhaichi Unfolded, the object will contain some 30 properties from four nesting levels. – André Reichelt Mar 11 '20 at 14:56

5 Answers5

5

You may use flatMap method alongwith map on property 'c':

var input = [{ a: 1, b: 2, c: [{ x: 10, y: 20 }, { x: 30, y: 40 }] }, { a: 3, b: 4, c: [{ x: 50, y: 60 }, { x: 70, y: 80 }] }];

const output = input.flatMap(obj =>
  obj.c.map(arr => ({a: obj.a, b: obj.b, x: arr.x, y: arr.y}))
);

console.log(output);
Bilal Siddiqui
  • 3,579
  • 1
  • 13
  • 20
  • 1
    Thank you for providing a solution. Yours seems to be identical to Bergi's idea., I've created a jsperf: https://jsperf.com/unfold-complex-object/1 – André Reichelt Mar 11 '20 at 14:42
3

Ideally a solution would require something to tell how far down to start classing the object as been a full object, a simple solution is just to pass the level you want. If you don't want to pass the level, you could do a check and if none of the properties have array's, then you would class this as a complete record, but of course that logic is something you would need to confirm.

If you want a generic version that works with multiple levels were you pass the level & using recursion you could do something like this ->

const a=[{a:1,b:2,c:[{x:10,y:20},{x:30,y:40}]},{a:3,b:4,c:[{x:50,y:60},{x:70,y:80}]}];


function flattern(a, lvl) {
  const r = [];
  function flat(a, l, o) {
    for (const aa of a) {
      o = {...o};
      for (const [k, v] of Object.entries(aa)) {
        if (Array.isArray(v) && l < lvl) flat(v, l + 1, o);
        else o[k] = v;
      }
      if (l === lvl) r.push(o);
    }
  }
  flat(a, 1);
  return r;
}

console.log(flattern(a, 2));
//console.log(flattern(a, 1));
Keith
  • 22,005
  • 2
  • 27
  • 44
  • Thank you for providing a solution. I've created a jsperf: https://jsperf.com/unfold-complex-object/1 – André Reichelt Mar 11 '20 at 14:42
  • @AndréReichelt Not really a fair jsperf comparison, as mine was the only generic solution :) , saying that at 14% slower doesn't seem bad for a generic solution either, so that's interesting.. – Keith Mar 11 '20 at 14:49
  • 1
    I actually decided to mark your idea as the solution, because it _is_ the only generic approach. It is the one with the least amount of maintenance in case I change anything on the C# class in the future. – André Reichelt Mar 11 '20 at 14:52
  • Would it be hard to modify your code, so that it flattens non-array properties as well? My structure contains two properties which are just single objects. – André Reichelt Mar 12 '20 at 09:47
  • @AndréReichelt I believe it shouldn't be too hard to modify,. You able to knock up an example input/output with this in mind. Might also be an idea to make into another question and reference this one if required. – Keith Mar 12 '20 at 11:19
2

A flatMap solution would look like this:

const result = myObj.flatMap(outer =>
  outer.c.map(inner =>
    ({a: outer.a, b: outer.b, x: inner.x, y: inner.y})
  )
);

Of course, if your object has multiple layers, not just two, and possibly even multiple or unknown properties that have such a nesting, you should try to implement a recursive solution. Or an iterative one, where you loop over an array of property names (for your example case, ["c"]) and apply the flattening level by level.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

One of the solution using reduce is:

const list = [{
    a: 1,
    b: 2,
    c: [{
        x: 10,
        y: 20
    }, {
        x: 30,
        y: 40
    }]
}, {
    a: 3,
    b: 4,
    c: [{
        x: 50,
        y: 60
    }, {
        x: 70,
        y: 80
    }]
}]

const flatten = (arr) => {
  return arr.reduce((flattened, item) => {
    return [
      ...flattened,
      ...item.c.reduce((flattenedItem, i) => {
        return [
          ...flattenedItem,
          {
            a: item.a,
            b: item.b,
            x: i.x,
            y: i.y
          }
        ]
      }, [])
    ]
  }, [])
}

console.log(flatten(list));
Vishal Sharma
  • 316
  • 1
  • 8
1

Using two reducers to flatten your structure

const input = [{
  a: 1,
  b: 2,
  c: [{
    x: 10,
    y: 20
  }, {
    x: 30,
    y: 40
  }]
}, {
  a: 3,
  b: 4,
  c: [{
    x: 50,
    y: 60
  }, {
    x: 70,
    y: 80
  }]
}]

const result = input.reduce((acc_0, x) => {
  return [...acc_0, ...x.c.reduce((acc_1, y) => {
    const obj = {
      a: x.a,
      b: x.b,
      x: y.x,
      y: y.y
    }
    acc_1.push(obj);
    return acc_1;
  }, [])]
}, []);

console.log(result)
EugenSunic
  • 13,162
  • 13
  • 64
  • 86