2

I have an array:

[
  {
    assignmentId:17,
    email:"[email protected]"
    expectation: "Make sure to proofread!",
    firstName:"John"
    id:23
    ignoreForFeedback: true
    lastName:"Smith"
    level:2
    levelFraction:null
    score:35
  },
  {
    assignmentId:17
    countsPerCategory: Array(4)
    email:"[email protected]"
    firstName:"John"
    frequentErrors: Array(5)
    id:23
    ignoreForGrading: true
    lastName:"Smith"
  },
  {
    assignmentId:17,
    email:"[email protected]"
    expectation: "cite sources",
    firstName:"Cindy"
    id:45
    ignoreForFeedback: true
    lastName:"Lee"
    level:2
    levelFraction:null
    score:32
  },
  {
    assignmentId:17
    countsPerCategory: Array(4)
    email:"[email protected]"
    firstName:"Cindy"
    frequentErrors: Array(5)
    id:45
    ignoreForGrading: true
    lastName:"Lee"
  }
]

I want to combine the Objects with the same 'id' into the same object within the array. Their common keys should also be combined (eg: 'firstName', 'email'). Can someone suggest the best way to do this? Either with ES6 or Lodash

1
  • 1
    See if my solution is what you're looking for Commented May 2, 2017 at 16:09

4 Answers 4

9

You can use lodash#groupBy to group all items in the array by id and then use lodash#map with an iteratee of lodash#assign that is wrapped with a lodash#spread to make the array callback as a list of arguments for lodash#assgin.

var result = _(array)
  .groupBy('id')
  .map(_.spread(_.assign))
  .value();

var array = [
  {
    assignmentId:17,
    email:"[email protected]",
    expectation: "Make sure to proofread!",
    firstName:"John",
    id:23,
    ignoreForFeedback: true,
    lastName:"Smith",
    level:2,
    levelFraction:null,
    score:35
  },
  {
    assignmentId:17,
    countsPerCategory: Array(4),
    email:"[email protected]",
    firstName:"John",
    frequentErrors: Array(5),
    id:23,
    ignoreForGrading: true,
    lastName:"Smith"
  },
  {
    assignmentId:17,
    email:"[email protected]",
    expectation: "cite sources",
    firstName:"Cindy",
    id:45,
    ignoreForFeedback: true,
    lastName:"Lee",
    level:2,
    levelFraction:null,
    score:32
  },
  {
    assignmentId:17,
    countsPerCategory: Array(4),
    email:"[email protected]",
    firstName:"Cindy",
    frequentErrors: Array(5),
    id:45,
    ignoreForGrading: true,
    lastName:"Lee"
  }
];

var result = _(array)
  .groupBy('id')
  .map(_.spread(_.assign))
  .value();
  
console.log(result);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

Here's an alternative solution that uses Array#filter which takes advantage of the 2nd argument of the Array#filter which gives context to the filter's callback function. We use the this context as a mechanism to store cached objects by their id and then use this to decide whether to retain these objects from the array or not.

var result = array.filter(function(v) {
  return this[v.id]?
    !Object.assign(this[v.id], v):
    (this[v.id] = v);
}, {});

var array = [
  {
    assignmentId:17,
    email:"[email protected]",
    expectation: "Make sure to proofread!",
    firstName:"John",
    id:23,
    ignoreForFeedback: true,
    lastName:"Smith",
    level:2,
    levelFraction:null,
    score:35
  },
  {
    assignmentId:17,
    countsPerCategory: Array(4),
    email:"[email protected]",
    firstName:"John",
    frequentErrors: Array(5),
    id:23,
    ignoreForGrading: true,
    lastName:"Smith"
  },
  {
    assignmentId:17,
    email:"[email protected]",
    expectation: "cite sources",
    firstName:"Cindy",
    id:45,
    ignoreForFeedback: true,
    lastName:"Lee",
    level:2,
    levelFraction:null,
    score:32
  },
  {
    assignmentId:17,
    countsPerCategory: Array(4),
    email:"[email protected]",
    firstName:"Cindy",
    frequentErrors: Array(5),
    id:45,
    ignoreForGrading: true,
    lastName:"Lee"
  }
];

var result = array.filter(function(v) {

  // does this `id` exist?
  return this[v.id]? 
  
    // assign existing object with the same id
    // from the `this` cache object. Make sure
    // to negate the resulting object with a `!`
    // to remove this value from the array
    !Object.assign(this[v.id], v):
    
    // Assign the value from the `this` cache.
    // This also retains this value from the existing
    // array
    (this[v.id] = v);
    
}, {});

console.log(result);
body > div { min-height: 100%; top: 0; }

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

1 Comment

@dedles Thanks, I also added a vanilla javascript as an alternative solution. If you find the overall solution to your liking, then you can mark this as the accepted answer.
3

You can use JavaScript's built in Array.reduce() method. The idea is you can create a map with the IDs and use lodash.merge() method (or whatever method you choose for merging objects) to merge all of the objects with the same ID into a single object. Then you can use .map() on the idMap you created to get the objects back into a single array.

    var data = [{
        assignmentId: 17,
        email: "[email protected]",
        expectation: "Make sure to proofread!",
        firstName: "John",
        id: 23,
        ignoreForFeedback: true,
        lastName: "Smith",
        level: 2,
        levelFraction: null,
        score: 35
      },
      {
        assignmentId: 17,
        countsPerCategory: Array(4),
        email: "[email protected]",
        firstName: "John",
        frequentErrors: Array(5),
        id: 23,
        ignoreForGrading: true,
        lastName: "Smith"
      },
      {
        assignmentId: 17,
        email: "[email protected]",
        expectation: "cite sources",
        firstName: "Cindy",
        id: 45,
        ignoreForFeedback: true,
        lastName: "Lee",
        level: 2,
        levelFraction: null,
        score: 32
      },
      {
        assignmentId: 17,
        countsPerCategory: Array(4),
        email: "[email protected]",
        firstName: "Cindy",
        frequentErrors: Array(5),
        id: 45,
        ignoreForGrading: true,
        lastName: "Lee"
      }
    ];

    var idMap = data.reduce(function(result, current) {
      if (result[current.id] == null) {
        result[current.id] = current;
      } else {
        _.merge(result[current.id], current);
      }

      return result;
    }, {});

    var results = Object.keys(idMap).map(function(key) {
      return idMap[key];
    });

    console.log(results);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

Comments

1

What I can suggest is to use a combination of forEach() and some() methods to iterate the array elements and test if the iterated object id is already processed or not.

This is the solution:

var merged = [];

arr.forEach(function(item) {
  var idx;
  var found = merged.some(function(el, i) {
    idx = el.id === item.id ? i : null;
    return el.id === item.id;
  });
  if (!found) {
    merged.push(item);
  } else if (idx !== null) {
    for (k in Object.keys(item)) {
      if (item.hasOwnProperty(k)) {
        merged[idx][k] = item[k];
      }
    }
  }
});

Working Demo:

var arr = [{
    assignmentId: 17,
    email: "[email protected]",
    expectation: "Make sure to proofread!",
    firstName: "John",
    id: 23,
    ignoreForFeedback: true,
    lastName: "Smith",
    level: 2,
    levelFraction: null,
    score: 35
  },
  {
    assignmentId: 17,
    countsPerCategory: [],
    email: "[email protected]",
    firstName: "John",
    frequentErrors: [],
    id: 23,
    ignoreForGrading: true,
    lastName: "Smith"
  },
  {
    assignmentId: 17,
    email: "[email protected]",
    expectation: "cite sources",
    firstName: "Cindy",
    id: 45,
    ignoreForFeedback: true,
    lastName: "Lee",
    level: 2,
    levelFraction: null,
    score: 32
  },
  {
    assignmentId: 17,
    countsPerCategory: [],
    email: "[email protected]",
    firstName: "Cindy",
    frequentErrors: [],
    id: 45,
    ignoreForGrading: true,
    lastName: "Lee"
  }
];
var merged = [];

arr.forEach(function(item) {
  var idx;
  var found = merged.some(function(el, i) {
    idx = el.id === item.id ? i : null;
    return el.id === item.id;
  });
  if (!found) {
    merged.push(item);
  } else if (idx !== null) {
    for (k in Object.keys(item)) {
      if (item.hasOwnProperty(k)) {
        merged[idx][k] = item[k];
      }
    }
  }
});
console.log(merged);

Comments

0

Thank you for your help everyone, but I ended up going with my own implementation.

        let ids = [];
        let combinedUsers = [];

        users.forEach(function (user) {
            ids.push(user.id);
        });

        ids = _.uniq(ids);
        ids.forEach(function(id){
            let user = users.filter(function(userObj){
                return id === userObj.id
            });

            if(user.length > 1){
                user = Object.assign(user[0], user[1]);
                combinedUsers.push(user);
            } else {
                combinedUsers.push(user[0]);
            }

        });
        return combinedStudents;

3 Comments

Well this is a good idea to use .filter() method, but your solution is mainly based on mine, you could at least upvoted it, but anyway​you shouldn't posted it as an answer, it should have been an edit to your question. :)
Maybe great minds think alike? I had written it before I noticed your answer. I upvoted you though, for your trouble.
No, I just wanted to say it would be better if you posted your answer as an Edit to your question. Tha'ts it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.