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

10
  • 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. Commented Mar 25, 2018 at 0:13
  • 1
    [].concat(...arr.map(item => item.parts)) Doesn't create that many intermediate Arrays as the approach with reduce Commented Mar 25, 2018 at 0:21
  • How does the result include this: part_id: 2, description: 'bar' when there's nothing in the input that resembles it? Commented Mar 25, 2018 at 0: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. Commented Mar 25, 2018 at 0:50
  • Possible duplicate of Merge/flatten an array of arrays in JavaScript? Commented Mar 25, 2018 at 0:58

6 Answers 6

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

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

Comments

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

1 Comment

I guess from the edit this is not it. I'll leave it up anyway.
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);

2 Comments

Sorry i do need to map the objects inside. I edited the question to show the format of the expected output
Simple, just change => outerObj.parts to => outerObj.parts[0] to select the first element in an array
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);

Comments

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

Comments

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.