使用 Lodash 按属性合并对象数组

Posted

技术标签:

【中文标题】使用 Lodash 按属性合并对象数组【英文标题】:Merge Array of Objects by Property using Lodash 【发布时间】:2016-12-31 20:09:49 【问题描述】:

我有两个对象数组,它们代表具有标签和值的电子邮件地址:

var original = [
  
    label: 'private',
    value: 'private@johndoe.com'
  ,
  
    label: 'work',
    value: 'work@johndoe.com'
  
];

var update = [
  
    label: 'private',
    value: 'me@johndoe.com'
  ,
  
    label: 'school',
    value: 'schhol@johndoe.com'
  
];

现在我想通过label 字段来比较和合并两个数组,这样结果会如下所示:

var result = [
  
    label: 'private',
    value: 'me@johndoe.com'
  ,
  
    label: 'work',
    value: 'work@johndoe.com'
  ,
  
    label: 'school',
    value: 'schol@johndoe.com'
  
]

我该怎么做?使用lodash?

【问题讨论】:

How to merge two arrays in javascript and de-duplicate items的可能重复 这并不是因为被比较的元素是对象还是字符串对可以使用的内容有很大的影响。 【参考方案1】:

_.unionBy(): 此方法与 _.union 类似,不同之处在于它接受 iteratee,它为每个数组的每个元素调用以生成计算唯一性的标准。 结果值从出现值的第一个数组中选择

var original = [
   label: 'private', value: 'private@johndoe.com' ,
   label: 'work', value: 'work@johndoe.com' 
];

var update = [
   label: 'private', value: 'me@johndoe.com' ,
   label: 'school', value: 'schol@johndoe.com' 
];

var result = _.unionBy(update, original, "label");

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

【讨论】:

很干净 @Andreas 小后续问题:我怎样才能达到相同的结果,但是当数组中的对象包含另一个对象作为值时?例如:[ label: 'private', value: propa: 'some text', propb: 12 ] 我希望结果合并,这样如果标签相同,则原始值的属性与新值的属性合并。 如果两个数组中的属性名不同怎么办? 如果我有 20 个或更多阵列怎么办?我找不到在循环中动态地将数组作为参数传递的方法。有什么想法吗?【参考方案2】:

将列表转换为以label 为键的对象,通过_.assign 合并它们,然后将其转换回数组。它甚至会在大多数浏览器上保留项目的顺序。

var original = [
  
    label: 'private',
    value: 'private@johndoe.com'
  ,
  
    label: 'work',
    value: 'work@johndoe.com'
  
];

var update = [
  
    label: 'private',
    value: 'me@johndoe.com'
  ,
  
    label: 'school',
    value: 'schol@johndoe.com'
  
];

console.log(
  _.map(
    _.assign(
      _.mapKeys(original, v => v.label),
      _.mapKeys(update, v => v.label)
    )
  )
);


// or remove more duplicated code using spread

console.log(
  _.map(
    _.assign(
      ...[original, update].map(
        coll => _.mapKeys(coll, v => v.label)
      )
    )
  )
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.js"></script>

【讨论】:

完美运行,但使用 _.unionBy() 的解决方案似乎更干净一些。谢谢:-) 是的,看到它的时候我都快哭了。太简单了,感觉lodash什么都有了:) 是的 ;-) 有时你只是失去了概述。【参考方案3】:

可能有点晚了,但是我看到的所有解决方案都没有正确连接两个数组,它们使用其中一个数组循环,并且第二个数组中的任何多余元素都不会被添加(假设这是是必需的)。

正确的方法是对两个数组进行排序并在两个数组中向前移动,合并匹配的元素并从两个数组中添加缺失的元素。 请在下面找到完整的解决方案。这也需要 O(n+m) 这是你能得到的最好的(没有排序本身的计算成本)。在我的代码中,我已经从数据库中排序了数据。

function mergeObjectsBasedOnKey(array1, array2, compareFn, mergeFn, alreadySorted) 
  var array1Index = 0;
  var array2Index = 0;

  const merged = [];

  if (!alreadySorted) 
    array1.sort(compareFn);
    array2.sort(compareFn);
  

  while (array1Index < array1.length && array2Index < array2.length) 
    var comparedValue = compareFn(array1[array1Index], array2[array2Index]);
    if (comparedValue === 0) 
      merged.push(mergeFn(array1[array1Index], array2[array2Index]));
      array1Index++;
      array2Index++;
     else if (comparedValue < 0) 
      merged.push(mergeFn(array1[array1Index]));
      array1Index++;
     else 
      merged.push(mergeFn(array2[array2Index]));
      array2Index++;
    
  
  while (array1Index < array1.length) 
    merged.push(mergeFn(array1[array1Index]));
    array1Index++;
  
  while (array2Index < array2.length) 
    merged.push(mergeFn(array2[array2Index]));
    array2Index++;
  
  return merged;



const array1 = [
    "id": 10,
    isArray1: true
  ,
  
    "id": 11,
    isArray1: true
  ,
  
    "id": 12,
    isArray1: true
  ,
];
const array2 = [
    "id": 8,
    isArray2: true
  ,
  
    "id": 11,
    isArray2: true
  ,
  
    "id": 15,
    isArray2: true
  ,
];

const result = mergeObjectsBasedOnKey(array1, array2, function(a, b) 
  return a.id - b.id;
, function(a, b) 
  if (b) 
    return _.merge(a, b);
  
  return _.merge(a, 
    isArray1: true,
    isArray2: true
  );
);

console.log(result);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"&gt;&lt;/script&gt;

结果是:

[  id: 8, isArray2: true, isArray1: true ,
   id: 10, isArray1: true, isArray2: true ,
   id: 11, isArray1: true, isArray2: true ,
   id: 12, isArray1: true, isArray2: true ,
   id: 15, isArray2: true, isArray1: true  ]

【讨论】:

【参考方案4】:

我知道这不是所要求的,但以防万一有人偶然发现此页面,这是您在 ramda 中执行此操作的方法:

var original = [
   label: 'private', value: 'private@johndoe.com' ,
   label: 'work', value: 'work@johndoe.com' 
];

var updated = [
   label: 'private', value: 'me@johndoe.com' ,
   label: 'school', value: 'schol@johndoe.com' 
];

unionWith(eqBy(prop('label')), updated, original);

【讨论】:

【参考方案5】:

如果您使用的 lodash 3.x 没有 _.unionBy(),您可以结合使用 _.union()_.uniq() 以获得相同的结果。

var original = [
   label: 'private', value: 'private@johndoe.com' ,
   label: 'work', value: 'work@johndoe.com' 
];

var update = [
   label: 'private', value: 'me@johndoe.com' ,
   label: 'school', value: 'schol@johndoe.com' 
];

var result = _.uniq(_.union(update, original), "label"); 

console.log(result);

【讨论】:

【参考方案6】:

这是使用 Lodash 合并两个对象的另一种方法:

let a = [
    content: 'aaa',
    name: 'bbb2'
  ,
  
    content: 'aad',
    name: 'ccd'
  
];

let b = [
    content: 'aaa',
    name: 'bbb'
  ,
  
    content: 'aad1',
    name: 'ccd1'
  
];

let c = [...a, ...b];

let d = _.uniq(c, function(data) 
  return data.content;
)

console.log(d);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"&gt;&lt;/script&gt;

【讨论】:

【参考方案7】:

也许有点晚了,但是我看到的所有解决方案都没有正确连接两个数组,它们使用其中一个数组循环,并且第二个数组中的任何多余元素都不会被添加(假设这是是必需的)。

我也有同样的观察,所以我自己整理了一些东西。这适用于我的用例,如果“标签”字段的值匹配,则合并每个对象:

const dataSetHashes = dataSets.map(dataSet => _.keyBy(dataSet, 'label'))
const resultHash = _.merge(
  ,
  ...dataSetLookups
)
const result = Object.values(resultLookup)

【讨论】:

以上是关于使用 Lodash 按属性合并对象数组的主要内容,如果未能解决你的问题,请参考以下文章

Lodash合并数组与字典对象键值

根据属性值用 lodash 过滤对象数组

如何使用 lodash 按嵌套属性分组?

使用 Lodash 进行深度合并

nodejs通过lodash合并去重由unixtime和Date组成的两个数组

使用 Lodash 从键值对数组中创建对象