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

4

我希望将两个具有相同属性的对象合并成一个数组。

以这个为例:

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() 不是正确的选择,因为它会覆盖对象属性,而我不希望它覆盖,我想要合并对象属性。有没有更好的方法可以实现这个功能?


所以,你真正需要的不是“相反”,而是在Stack Overflow上有很多关于如何做到这一点的示例。 - Jaromanda X
@JaromandaX 感谢您纠正我的标题... 除了使用Object.assign(),还有更好的方法吗? - Ronaldo
Object.assign是用于对象的,你需要的是合并数组。甚至可以简单地将其添加到数组中。 - Keith
既然 Object assign 没有做到您想要的,那么我怀疑还有更好的方法——可能涉及到3或4种不同的方式。 - Jaromanda X
你的期望结果是不可能的,因为你期望 JSON.stringify([result]) 是一个对象数组,这需要 result 是多个对象 - 显然是不可能的。 - Jaromanda X
8个回答

6

像往常一样,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; }


哇,我会说这段代码对我有用。但是它只能在第一层级别上合并我的数组,也就是父级。它无法处理嵌套的子级。 - Ronaldo
@Ronaldo...如果有一个展示/描述新需求的例子,我们或许能够相应地调整我们的方法。 - Peter Seliger
你可以查看我发布的这个答案。使用了你的方法,我进行了改进。还有什么地方可以进行改进吗?https://dev59.com/dKbja4cB1Zd3GeqPeEtm#47027531 - Ronaldo

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。现在这些也将被合并。 - Keith

2

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

请参阅以下链接,了解为什么我在node console.log()中使用util打印所有对象:

如何在Node.js的console.log()中获取完整对象,而不是'[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 in turn takes array as argument
 */
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))})();

2
/* 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));

1
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)

3
虽然这段代码可能是解决方案,但包括解释确实有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而那些人可能不知道您提出代码建议的原因。请在翻译时保持原意,使内容通俗易懂。 - yivi

0

这里是一个示例草图,展示如何做到这一点。它利用了映射类型,使用您的 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));


0

在 @Peter Seliger 的答案 这里 的基础上,我得出了以下方法来合并具有深度嵌套子级的数组。

给定以下对象:

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" }
    //                                            ]  
    //               }
    //    ]
    // }]

然而,这可能并不太高效,因为如果我的树嵌套更多级别(例如,子子孙孙、子子子孙孙等),我就需要不断添加到合并子节点/子子节点的方法中。

有没有更有效的方法来解决这个问题呢?


3
好的,现在我明白你在评论中提到我的第一个答案/方法时指的是什么了。正如你自己已经提到的那样,你的解决方案不是通用的,也不是很高效。所以请对自己诚实,不要接受自己的答案。作为回报,我将编辑我的答案并添加一个第二次迭代步骤来完善这个解决方案,使其更符合你的要求。 - Peter Seliger

0
const object1 = {
    id:1,
    name:'a',
}

const object2 = {
    id:3,
    name:'b',
}

const object3 = {
    id:1,
    name:'c',
}

const originArr = [object1, object2, object3]
const idArr = [object1.id, object2.id, object3.id]
const newIdArr = []

for (let id of idArr) {
    if (newIdArr.indexOf(id)) newIdArr.push(id)
}

const result = newIdArr.map(id => {
    let names = []
    for (obj of originArr) {
        if (id === obj.id) names.push(obj.name)
    }

    return { id, names }
})

console.log(result)

欢迎来到StackOverflow!请注意,这会显著改变输入结构,我不确定它是否能解决原始问题,除非我自己尝试。请参阅如何创建可运行的堆栈片段?以了解如何更轻松地进行测试。 - Scott Sauyet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接