如何将具有相同属性的对象合并到一个数组中?

Posted

技术标签:

【中文标题】如何将具有相同属性的对象合并到一个数组中?【英文标题】:How to merge objects with the same properties into an Array? 【发布时间】:2018-04-08 19:55:14 【问题描述】:

我想将两个具有相同属性的对象合并到一个数组中。

以此为例:

object1 = "id":1,
           "name":name1,
           "children":["id":2,"name":name2]
          ;
object2 = "id":3,
           "name":name3,
           "children":["id":4,"name":name4]
          ;
object3 = "id":1,
           "name":name1,
           "children":["id":6,"name":name6]
          ;
var result = Object.assign(result,object1,object2,object3);

预期结果:

JSON.stringify([result]) =[
                           "id":1,
                            "name":name1,
                            "children":["id":2,"name":name2,
                                       "id":6,"name":name6]
                           ,
                           "id":3,
                            "name":name3,
                            "children":["id":4,"name":name4]
                           
                          ]

实际结果:

JSON.stringify([result]) = [
                            "id":3,
                             "name":name3,
                             "children":["id":4,"name":name4]
                            
                           ]

似乎 Object.assign() 不是要走的路……因为它会覆盖,我不希望它覆盖,我希望它们合并。有正确的方法吗?

【问题讨论】:

所以,这并不是您真正要寻找的“对立面” - 有很多示例说明如何仅在堆栈溢出时执行此操作,真的 @JaromandaX 感谢您更正我的标题...是的,有没有比使用 Object.assign() 更好的方法来做到这一点? Object.assign 用于对象,您的目标是合并数组。或者更简单,只是添加到数组中。 由于 Object assign 不能满足您的要求,所以我怀疑有更好的方法 - 可能有 3 或 4 种不同的方法 您的预期结果是不可能的,因为您希望 JSON.stringify([result]) 是一个对象数组,这需要 result 是多个对象 - 显然不可能 【参考方案1】:

像往常一样,Array.prototype.reduce 为类似的方法提供了良好的基础,例如这个……

var obj1 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 2, "name": "name2" ]
;
var obj2 = 
  "id": 3,
  "name": "name3",
  "children": [ "id": 4, "name": "name4" ]
;
var obj3 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 6, "name": "name6" ]
;
// Expected result: [
//   "id": 1,
//   "name": name1,
//   "children": [
//      "id": 2, "name": "name2" ,
//      "id": 6, "name": "name6" 
//   ]
// , 
//   "id": 3,
//   "name": "name3",
//   "children": ["id": 4, "name": "name4" ]
// ]

function mergeEquallyLabeledTypes(collector, type) 
  var key = (type.name + '@' + type.id); // identity key.
  var store = collector.store;
  var storedType = store[key];
  if (storedType)  // merge `children` of identically named types.
    storedType.children = storedType.children.concat(type.children);
   else 
    store[key] = type;
    collector.list.push(type);
  
  return collector;


var result = [obj1, obj2, obj3].reduce(mergeEquallyLabeledTypes, 

  store:  ,
  list:   []

).list;

console.log('result : ', result);
.as-console-wrapper  max-height: 100%!important; top: 0; 

编辑备注

在被告知需要处理嵌套模式的更改需求后,我会将我提供的第一个方法更改为通用解决方案。这不会那么困难,因为在数据结构中存在一般重复的模式。因此,我只需要使已经存在的 reducer 函数自递归。在任何提供的列表上完成一个完整的归约循环后,将触发一个递归步骤...

var obj1 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 2, "name": "name2", "children": [ "id": 8, "name": "name8" ] ]
;
var obj2 = 
  "id": 3,
  "name": "name3",
  "children": [ "id": 4, "name": "name4", "children": [ "id": 9, "name": "name9" ] ]
;
var obj3 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 6, "name": "name6", "children": [ "id": 10, "name": "name10" ] ]
;
var obj4 = 
  "id": 3,
  "name": "name3",
  "children": [ "id": 4, "name": "name4", "children": [ "id": 11, "name": "name11" ] ]
;

function mergeEquallyLabeledTypesRecursively(collector, type, idx, list) 
  var key = (type.name + '@' + type.id); // identity key.
  var store = collector.store;
  var storedType = store[key];
  if (storedType)  // merge `children` of identically named types.
    storedType.children = storedType.children.concat(type.children);
   else 
    store[key] = type;
    collector.list.push(type);
  
  // take repetitive data patterns into account ...
  if (idx >= (list.length - 1)) 
    collector.list.forEach(function (type) 

      // ... behave recursive, when appropriate.
      if (type.children) 
        type.children = type.children.reduce(mergeEquallyLabeledTypesRecursively, 

          store:  ,
          list:   []

        ).list;
      
    );
  
  return collector;


var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypesRecursively, 

  store:  ,
  list:   []

).list;

console.log('result : ', result);
.as-console-wrapper  max-height: 100%!important; top: 0; 

【讨论】:

哇,我想说这个 sn-p 对我有用。但这只能在第一级合并我的数组,即父级。它不适用于嵌套的孩子。 @Ronaldo ...如果有一个示例可以显示/描述新要求,我们也许可以相应地调整我们的方法。 你可以查看我发布的这个答案。使用你的方法,我已经建立了它。无论如何,这可以改进吗? ***.com/a/47027531/7124504【参考方案2】:

这可能是你的后缀,请注意它不是递归的现在是递归的。但是您的示例数据似乎并不存在。

const object1 = "id":1,
           "name":"name1",
           "children":["id":2,"name":"name2"]
          ;
const object2 = "id":3,
           "name":"name3",
           "children":["id":4,"name":"name4"]
          ;
const object3 = "id":1,
           "name":"name1",
           "children":[
              "id":6,"name":"name6",
              "id":7,"name":"name7",
              "id":6,"name":"name6"
           ]
          ;
    
function merge(arr) 
  const idLinks = ;
  const ret = [];
  arr.forEach((r) => 
    if (!idLinks[r.id]) idLinks[r.id] = [];
    idLinks[r.id].push(r);
  );
  Object.keys(idLinks).forEach((k) => 
    const nn = idLinks[k];
    const n = nn[0];
    for (let l = 1; l < nn.length; l ++) 
      if (nn[l].children) 
        if (!n.children) n.children = [];
        n.children = n.children.concat(nn[l].children);
            
        
    if (n.children && n.children.length) n.children = merge(n.children);
    ret.push(n);
  );
  return ret;

         
var result = merge([object1,object2,object3]);

console.log(result);

【讨论】:

刚刚做了一个快速更新来处理递归 ID,如果你查看第二个 id 1,它有 2 个 id 6。这些现在也将被合并。【参考方案3】:

以 es6 标准的函数式编程方式。我假设 children 数组也包含重复项。我将代码封闭在闭包中。

查看以下链接为什么我使用util打印节点console.log()中的所有对象

How can I get the full object in Node.js's console.log(), rather than '[Object]'?

(function() 

'use strict';

const util = require('util');

/** string constants */
const ID = 'id';
const CHILDREN = 'children';

/* Objects to modify */
const object1 = 
    "id": 1,
    "name": "name1",
    "children": [
         "id": 2, "name": "name2" ,
         "id": 5, "name": "name5" ,
         "id": 7, "name": "name7" 
    ]
;
const object2 = 
    "id": 3,
    "name": "name3",
    "children": [
         "id": 4, "name": "name4" 
    ]
;
const object3 = 
    "id": 1,
    "name": "name1",
    "children": [
         "id": 5, "name": "name5" ,
         "id": 6, "name": "name6" 
    ]
;

/**
 * Concates the arrays
 * @param  array   - a
 * @param  array   - b
 */
const merge = (a, b) => 
    return a.concat(b);
;

/**
 * Removes Duplicates from the given array based on ID
 * @param   array  - array to remove duplicates
 * @return  array  - array without duplicates
 */
const removeDuplicates = (arr) => 
    return arr.filter((obj, pos, arr) => 
        return arr.map((m) => 
            return m[ID];
        ).indexOf(obj[ID]) === pos;
    );


/**
 * Groups items in array with particular key
 * Currying technique
 * @param   prop      - key to group
 * @return  () =>   - Method which inturns takes array as arguement
 */
const groupBy = (prop) => (array) => 
    return array.reduce((groups, item) => 
        const val = item[prop];
        groups[val] = groups[val] || [];
        groups[val].push(item);
        return groups;
    , );


/**
 * Object containing grouped-items by particuar key
 */
const grouped = groupBy(ID)([object1, object2, object3]);

/**
 * Removing the duplicates of children
 * Remember map also mutates the array of objects key's value
 * but not data type
 */
Object.keys(grouped).map((key, position) => 
    grouped[key].reduce((a, b) => 
        a[CHILDREN] = removeDuplicates(a[CHILDREN].concat(b[CHILDREN]));
    );
);

/**
 * Desired final output
 */
const final = Object.keys(grouped)
    .map((key) => removeDuplicates(grouped[key]))
    .reduce(merge, []);

console.log(util.inspect(final, false, null)))();

【讨论】:

【参考方案4】:
/* There are two cases : 

 a) No duplicate children 
 b) Duplicate children either in (same object || different object|| both) 
*/ 

/* =============== */

/* Case a) */
const util = require('util');
var object1 = 
    "id": 1,
    "name": "name1",
    "children": [ "id": 2, "name": "name2" ]
;

var object2 = 
    "id": 3,
    "name": "name3",
    "children": [ "id": 4, "name": "name4" ]
;

var object3 = 
    "id": 1,
    "name":"name1",
    "children":["id":6,"name":"name6"]
;


var arr = [object1,object2,object3];
var uniqueIds = [];
var filteredArray = [];
var uniqueId='';



    arr.map((item,i,array)=>
    uniqueId =uniqueIds.indexOf(item.id);
    uniqueId = uniqueId+1;
    uniqueIds = [...uniqueIds,item.id];
    if(!uniqueId)
        filteredArray[i] = item;
    
    if(uniqueId)
        filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
     
);

console.log(util.inspect(filteredArray,false,null));

/* ============================================ 
 Case b) 

 Dealing with the worst case of having duplicate children in both same 
 and different objects
*/

    object1 = "id":1,
    "name":'name1',
    "children":["id":2,"name":'name2',
    "id":2,"name":'name2']
    ;
    object2 = "id":3,
    "name":'name3',
    "children":["id":4,"name":'name4']
    ;
    object3 = "id":1,
    "name":'name1',
    "children":["id":6,"name":'name6',
    "id":7,"name":'name7',
    "id":2,"name":'name2']
    ;

    arr = [object1,object2,object3];
    uniqueIds = [];
    uniqueId = '';




arr.map((item,i,array)=>
    uniqueId =uniqueIds.indexOf(item.id);
    uniqueId = uniqueId+1;
    uniqueIds = [...uniqueIds,item.id];
    if(!uniqueId)
        filteredArray[i] = item;
    
    if(uniqueId)
        filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
     
    /*Removing duplicate children entries*/
    filteredArray[uniqueIds.indexOf(item.id)]['children'] = filteredArray[uniqueIds.indexOf(item.id)]['children']
    .filter((elem, index, self) => self.findIndex((t) => return t.id === elem.id) === index)
)

console.log(util.inspect(filteredArray,false,null));

【讨论】:

【参考方案5】:
const object1 = 
      "id":1,
      "name":"name1",
      "children":["id":2,"name":"name2"]
    ;
    const object2 = 
      "id":3,
      "name":"name3",
      "children":["id":4,"name":"name4"]
    ;
    const object3 = 
      "id":1,
      "name":"name1",
      "children":["id":6,"name":"name6"]
    ;

    var array = [object1,object2,object3];
    var array2 = [object1,object2,object3];

    function uniquearray(obj)
      var result =[];
      for(var i=0;i<array.length;i++)
        if(obj.id == array[i].id)
          result.push(array[i])
          array.splice(i,1)
        
      
      return result;
    

    var arrayofuniarrays = []
    for(var i=0;i<array2.length;i++)
      arrayofuniarrays.push(uniquearray(array2[i]))
    

    for(var i=0;i<arrayofuniarrays.length;i++)
      for(var j=1;j<arrayofuniarrays[i].length; j++)
        arrayofuniarrays[i][0].children.push(arrayofuniarrays[i][j].children)
        arrayofuniarrays[i].splice(j,1)
      
    
    var resul = arrayofuniarrays.reduce(function(a, b)return a.concat(b),[])
    console.log(resul)

【讨论】:

虽然此代码 sn-p 可能是解决方案,但 including an explanation 确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。【参考方案6】:

这是一个如何执行此操作的草图示例。它利用使用您的id 作为键的映射类型来确保每个项目只出现一次。它根据 id 将所有子元素添加到数组中。

如果您需要对孩子强制执行相同的行为,您可以使用相同的技术。

我已将其拆分为多个迭代,以向您展示正在播放的各个部分。

通常,如果可以的话,避免创建需要压缩备份的对象会更有效。

    const object1 = 
        "id": 1,
        "name": "name1",
        "children": [ "id": 2, "name": "name2" ]
    ;
    
    const object2 = 
        "id": 3,
        "name": "name3",
        "children": [ "id": 4, "name": "name4" ]
    ;
    
    const object3 = 
        "id": 1,
        "name":"name1",
        "children":["id":6,"name":"name6"]
    ;
    
    const all = [object1, object2, object3];
    
    // Use a map like a dictionary to enforce unique keys
    const mapped = ;
    for (let obj of all) 
        if (!mapped[obj.id]) 
            mapped[obj.id] = obj;
            continue;
        
    
        mapped[obj.id].children.push(obj.children);
    
    console.log('Mapped ==> '+JSON.stringify(mapped));
    
    // If you want to convert the mapped type to an array
    const result = [];
    for (let key in mapped) 
        result.push(mapped[key]);
    
    
    console.log('Array ==> '+JSON.stringify(result));

【讨论】:

【参考方案7】:

在@Peter Seliger 的回答here 的基础上,我使用以下方法派生了将数组与深度嵌套的子元素合并。

给定以下对象:

var obj1 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 2, "name": "name2", children:[ "id":8, "name": "name8" ]  ]
;
var obj2 = 
  "id": 3,
  "name": "name3",
  "children": [ "id": 4, "name": "name4", children:[ "id":9, "name": "name9" ] ]
;
var obj3 = 
  "id": 1,
  "name": "name1",
  "children": [ "id": 6, "name": "name6", children:[ "id":10, "name": "name10" ] ]
;
var obj4 = 
  "id": 3,
  "name": "name3",
  "children": [ "id": 4, "name": "name4", children:[ "id":11, "name": "name11" ] ]
;    

首先我们合并父母

function mergeEquallyLabeledTypes(collector, type) 
  var key = (type.name + '@' + type.id); // identity key.
  var store = collector.store;
  var storedType = store[key];
  if (storedType)  // merge `children` of identically named types.
    if(storedType.children)
       storedType.children = storedType.children.concat(type.children);
   else 
    store[key] = type;
    collector.list.push(type);
  
  return collector;


var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypes,     
  store:  ,
  list:   []    
).list;

然后我们合并孩子和子孩子(如果有的话)。

for(let i=0; i<result.length; i++)
   var children = result[i].children;
   if(children)
     var reducedChildren = children.reduce(mergeEquallyLabeledTypes, store: ,    list: []).list;

      for(let j=0; j<reducedChildren.length; j++)
        var subchildren = reducedChildren[j].children;
        if(subchildren)
           var reducedSubchildren = subchildren.reduce(mergeEquallyLabeledTypes, store: ,    list: []).list;
            reducedChildren[j].children = reducedSubchildren;
                                   
      

     result[i].children = reducedChildren;
                       
 

最终结果将是我将解析到我的网站中的内容。

console.log('result : ', result);

我能够得到预期的结果。

    // result: [
    //   "id": 1,
    //   "name": name1,
    //   "children": [
    //      "id": 2, "name": "name2", children:[ "id":8, "name": "name8" ] ,
    //      "id": 6, "name": "name6", children:[ "id":10, "name": "name10" ] 
    //   ]
    // , 
    //   "id": 3,
    //   "name": "name3",
    //   "children": ["id": 4, "name": "name4", children:[
    //                                               "id":9, "name": "name9" ,
    //                                               "id":11, "name": "name11" 
    //                                            ]  
    //               
    //    ]
    // ]

但是,这可能不太有效,因为如果我的树嵌套更多级别,我需要继续添加子/子子合并方法。 (例如 subsubchildren、subsubsubchildren 等等...)

有没有更有效的方法来做到这一点?

【讨论】:

好的,现在我明白了您在对我的第一个答案/方法的评论中所指的内容。正如您已经提到自己的那样,您的解决方案不是通用的,也不是那么有效。所以请对自己诚实,不要接受自己的答案。作为回报,我将 edit my answer and add a second iteration step to the provided approach 使此解决方案对您的要求具有通用性。

以上是关于如何将具有相同属性的对象合并到一个数组中?的主要内容,如果未能解决你的问题,请参考以下文章

合并具有相同属性值的json对象c#

将具有相同键的 JSON 对象合并在一起

使用值将数组中对象的属性合并在一起并删除重复项

如何在 mongodb 的数组中合并具有相同键的对象?

如何从一个数组中获取具有其他相同属性的对象?

如何组合 2 个对象并在其中合并数组 [重复]