2

I have an array of objects, where each object has a property whose value is another array, like this

[
  {
    location: 'somewhere',
    location_id: 1,
    parts: [
      {
        part_id: 1,
        description: 'foo',
        qty: 1,
      }
    ]
  }
]

I need to map these arrays into a single array like this

[
  {
    part_id: 1,
    description: 'foo',
    qty: 1
  },
  {
    part_id: 2,
    description: 'bar',
    qty: 1
  }
]

I tried using reduce like newArr: arr.reduce((a,b) => a.parts.concat(b.parts)) but get the error 'Cannot read property concat of undefined.' I also tried providing the initialValue argument of reduce as an empty array but same error.

As a bonus, I will eventually need the final array to not contain duplicates of parts: I.e. if a part is in two locations, it would just combine the quantity.

Open to solutions using es6 but needs to not modify original array

wizloc
  • 2,202
  • 4
  • 28
  • 54
  • 2
    You will need to specify an initial value. The first parameter to `reduce` callback is the accumulator array, so it won't have a `parts` property. – 4castle Mar 25 '18 at 00:13
  • 1
    `[].concat(...arr.map(item => item.parts))` Doesn't create that many intermediate Arrays as the approach with `reduce` – Thomas Mar 25 '18 at 00:21
  • How does the result include this: `part_id: 2, description: 'bar'` when there's nothing in the input that resembles it? – zer00ne Mar 25 '18 at 00:49
  • I just gave one object in the array as an example to show the structure, I didn't think it would be necessary to list out multiple objects. – wizloc Mar 25 '18 at 00:50
  • Possible duplicate of [Merge/flatten an array of arrays in JavaScript?](https://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript) – BlackICE Mar 25 '18 at 00:58
  • He/she was asking how to flatten array of arrays. I am asking how to immutably map array inside object inside array of objects into single array, and also how to do so while avoiding duplicates inside said array (though nobody has answered with this yet) . – wizloc Mar 25 '18 at 01:03
  • @wizloc for that you need to explain how you define a duplicate, and how exactly two/multiple "identical" objects should be reduced to a single result. *"if a part is in two locations, it would just combine the quantity"* doesn't explain much for someone who's not familiar with your project and data. Like your snippet doesn't even contain any "quantities" – Thomas Mar 25 '18 at 02:04
  • @Thomas yes I obviously forgot a crucial piece of information. I edited the question – wizloc Mar 25 '18 at 02:07
  • @wizloc ok, and what about the questions I've asked? What are duplicates? And how to combine them? there are multiple things what you could mean with these terms (duplicate and combine). Like if they have the same part_id but different descriptions, are they duplicates? what about the other way around? are there other properties that would define two objects as duplicates? how to combine two objects with the same part_id but different descriptions? ... Your request is anything but clear – Thomas Mar 25 '18 at 02:13
  • Having `{ part_id: 1, description: 'foo' }` and `{ part_id: 1, description: 'bar' }` would make the part_id redundant and I think most people would assume a duplicate is when you have `[ { part_id: 1, description: 'foo', qty: 1 }, { part_id: 1, description: 'foo', qty: 3 } ]`, which when resolved would combine the quantities and give `{ part_id: 1, description: 'foo', qty: 4 }`. If there were any stipulations other than the quantity I would have written so – wizloc Mar 25 '18 at 02:31

6 Answers6

2

This solution uses reduce and forEach to combine all objects in sub-arrays into a Map, with part_d as key, and the qty the result of the combined quantities. When a new part_id is found, a new object is created by spreading the part into a new object, and overriding qty with 0.

The Map is then converted back to an array by spreading the Map.values() iterator:

const data = [{"location":"somewhere","location_id":1,"parts":[{"part_id":1,"description":"foo","qty":1}]},{"location":"somewhere2","location_id":2,"parts":[{"part_id":1,"description":"foo","qty":3},{"part_id":2,"description":"bar","qty":1}]}];

const result = [...
  data.reduce((r, { parts }) => {
    (parts || []).forEach((o) => {
      r.has(o.part_id) || r.set(o.part_id, { ...o, qty: 0 });

      r.get(o.part_id).qty += o.qty;
    });

    return r;
  }, new Map())
.values()];

console.log(result);
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
1

If you're sure you want to map the arrays and not the objects inside:

const input = [
  {
    location: 'somewhere',
    location_id: 1,
    parts: [
      {
        part_id: 1,
        description: 'something'
      }
    ]
  },
  {
    location: 'somewhere2',
    location_id: 22,
    parts: [
      {
        part_id: 2,
        description: 'something'
      }
    ]
  }
];

const mapped = input.map(outerObj => outerObj.parts[0]);
console.log(mapped);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
1

This adds all part objects into the base and removes the parts array. It will modify the old object.

let arr = [
  {
    location: 'somewhere',
    location_id: 1,
    parts: [
      {
        part_id: 1,
        description: 'something'
      }
    ]
  }
]

arr.map(m => {
  m.parts.forEach(p => Object.assign(m, p))
  delete m.parts;
})

console.log(arr);
Jorg
  • 7,219
  • 3
  • 44
  • 65
1

You can use array#reduce to first join all your parts array of each object. Then use array#reduce to group each object in the result array on part_id and description and sum up the qty for each unique object. Then get all the values from this object.

const input = [ { location: 'somewhere', location_id: 1, parts: [ { part_id: 1, description: 'foo', qty: 1 } ] }, { location: 'somewhere2', location_id: 22, parts: [ { part_id: 2, description: 'something', qty: 3 } ] }, { location: 'somewhere2', location_id: 22, parts: [ { part_id: 2, description: 'something', qty: 4 } ] } ],
      result = Object.values(input.reduce((r, {parts}) => r.concat(parts),[])
        .reduce((r,o) => {
          r[`${o.part_id}_${o.description}`] = r[`${o.part_id}_${o.description}`] || {...o, qty: 0};
          r[`${o.part_id}_${o.description}`].qty += o.qty;
          return r;
        },{}));
console.log(result);
Hassan Imam
  • 21,956
  • 5
  • 41
  • 51
0

This question may have two scenarios :

  1. each location have single object under parts array.

    DEMO

let jsonObj = [
  {
    location: 'somewhere',
    location_id: 1,
    parts: [
      {
        part_id: 1,
        description: 'foo',
        qty: 1,
      }
    ]
  },
  {
    location: 'somewhere',
    location_id: 2,
    parts: [
      {
        part_id: 2,
        description: 'foo',
        qty: 1,
      }
    ]
  }
];

let res = jsonObj.map(obj => obj.parts[0]);
console.log(res);
  1. Single location have multiple objects under parts array.

    DEMO

let jsonObj = [
  {
    location: 'somewhere',
    location_id: 1,
    parts: [
      {
        part_id: 1,
        description: 'foo',
        qty: 1,
      },
      {
        part_id: 2,
        description: 'foo',
        qty: 1,
      }
    ]
  }
];

let res = jsonObj[0].parts;
console.log(res);
Debug Diva
  • 26,058
  • 13
  • 70
  • 123
0

I had a similar situation. What worked for me is defining a new array and then array.push(part) from inside a double map function:

const input = [{
    location: 'somewhere',
    location_id: 1,
    parts: [{
        part_id: 1,
        description: 'something'
      },
      {
        part_id: 2,
        description: 'something'
      }
    ]
  },
  {
    location: 'somewhere2',
    location_id: 22,
    parts: [{
        part_id: 3,
        description: 'something'
      },
      {
        part_id: 4,
        description: 'something'
      }
    ]
  }
];

if this is your input..

var list = []
input.map(item => {
  item.parts.map(part => {
    list.push(part);
  });
});
Dharman
  • 30,962
  • 25
  • 85
  • 135