最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Merge objects concatenating values, using lodash - Stack Overflow

programmeradmin4浏览0评论

I'm trying to manipulate this sample array of objects.

[ { name: 'John Wilson',
    id: 123,
    classes: ['java', 'c++']},
  { name: 'John Wilson',
    id: 123,
    classes: 'uml'},
   { name: 'Jane Smith',
    id: 321,
    classes: 'c++'} ]

What I need to do is to merge objects with the same 'id', concatenating 'classes' and keeping one 'name'.

The result should be:

[ { name: 'John Wilson',
    id: 123,
    classes: ['java', 'c++', 'uml']},
   { name: 'Jane Smith',
    id: 321,
    classes: 'c++'} ]

I tried using .merge but it doesn't concatenate the values from 'classes', it just keeps the values from the last equal object.

What is the simplest way to do that, using lodash?

I'm trying to manipulate this sample array of objects.

[ { name: 'John Wilson',
    id: 123,
    classes: ['java', 'c++']},
  { name: 'John Wilson',
    id: 123,
    classes: 'uml'},
   { name: 'Jane Smith',
    id: 321,
    classes: 'c++'} ]

What I need to do is to merge objects with the same 'id', concatenating 'classes' and keeping one 'name'.

The result should be:

[ { name: 'John Wilson',
    id: 123,
    classes: ['java', 'c++', 'uml']},
   { name: 'Jane Smith',
    id: 321,
    classes: 'c++'} ]

I tried using .merge but it doesn't concatenate the values from 'classes', it just keeps the values from the last equal object.

What is the simplest way to do that, using lodash?

Share Improve this question edited Dec 12, 2016 at 2:32 R. Favero asked Dec 10, 2016 at 2:21 R. FaveroR. Favero 331 silver badge6 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 3

The function you're looking for is _.uniqWith, with a special twist which I will explain in a minute.

_.uniqWith is a lot like _.uniq in that it generates a unique array, but it allows you to pass your own custom parator function that will be called to determine what counts as "equality."

Sane programmers would understand that this parator should be side-effect free. The way this code works is by breaking that rule, and using a parison function that does extra magic behind the scenes. However, this results in very concise code that will work no matter how many of these objects are in your array, so I feel like the transgression is well-justified.

I named the parator function pareAndMerge so as not to hide its impure nature. It will merge both classes arrays and update the relevant property on both objects, but only if their id values are identical.

function merge(people) {
  return _.uniqWith(people, pareAndMerge)
}

function pareAndMerge(first, second) {
    if (first.id === second.id) {
        first.classes = second.classes = [].concat(first.classes, second.classes)
        return true
    }
    return false
}


var people = [{
  name: 'John Wilson',
  id: 123,
  classes: ['java', 'c++']
}, {
  name: 'John Wilson',
  id: 123,
  classes: 'uml'
}, {
  name: 'Jane Smith',
  id: 321,
  classes: 'c++'
}]

console.log(merge(people))
<script src="https://cdnjs.cloudflare./ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

An aside: You were missing square brackets around your original classes lists. I made sure that the code above doesn't care whether or not the classes property holds a single string or an array of strings, though, just in case.

Using ES6 you can do so with a Map to hold the unique values, Array#reduce to populate it, and the spread operator with Map#values to convert it back to array:

const arr = [{"name":"John Wilson","id":123,"classes":["java","c++"]},{"name":"John Wilson","id":123,"classes":"uml"},{"name":"Jane Smith","id":321,"classes":"c++"}];

const result = [...arr.reduce((hash, { id, name, classes }) => {
  const current = hash.get(id) || { id, name, classes: [] };
  
  classes && (current.classes = current.classes.concat(classes));
  
  return hash.set(id, current);
}, new Map).values()];

console.log(result);

Not sure using lodash... here's a way to do it with normal JS:

var bined = arr.reduce(function(a, item, idx) {
    var found = false;
    for (var i = 0; i < a.length; i++) {
        if (a[i].id == item.id) {
            a[i].classes = a[i].classes.concat(item.classes);
            found = true;
            break;
        }
    }

    if (!found) {
        a.push(item);
    }

    return a;
}, []);

Fiddle: https://jsfiddle/6zwr47mt/

use _.mergeWith to set merging customizer

_.reduce(data, function(result, item) {
    item = _.mergeWith(
        item,
        _.find(result, {id: item.id}),
        function(val, addVal) {
            return _.isArray(val) ? _.concat(val, addVal) : val;
        });
    result = _.reject(result, {id: item.id})
    return _.concat(result, item);
}, []);

The following algorithm is not the best one but at least I know what it does :-)

console.log(clean(data));

function clean (data) {
  var i, x, y;
  var clean = [];
  var m = clean.length;
  var n = data.length;
  data.sort((x, y) => x.id - y.id);
  for (i = 0; i < n; i++) {
    y = data[i];
    if (i == 0 || x.id != y.id) {
      clean.push(x = clone(y)), m++;
    } else {
      clean[m - 1] = merge(x, y);
    }
  }
  return clean;
}

function clone (x) {
  var z = {};
  z.id = x.id;
  z.name = x.name;
  z.classes = x.classes.slice();
  return z;
}

function merge (x, y) {
  var z = {};
  z.id = x.id;
  z.name = x.name;
  z.classes = unique(
    x.classes.concat(y.classes)
  );
  return z;
}

function unique (xs) {
  var i, j, n;
  n = xs.length;
  for (i = 1; i < n; i++) {
    j = 0; while (j < i && xs[i] !== xs[j]) j++;
    if (j < i) swap(xs, i, n - 1), i--, n--;
  }
  return xs.slice(0, n);
}

function swap (xs, i, j) {
  var x = xs[i];
  xs[i] = xs[j];
  xs[j] = x;
}
<script>
  var data = [{
    id: 123,
    name: 'John Wilson',
    classes: ['java', 'c++']
  }, {
    id: 123,
    name: 'John Wilson',
    classes: ['uml', 'java']
  }, {
    id: 321,
    name: 'Jane Smith',
    classes: ['c++']
  }];
</script>

发布评论

评论列表(0)

  1. 暂无评论