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

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.