将具有相同键的JavaScript对象合并到数组中

70

如何在JavaScript对象中,将具有共同键的数组内容合并起来?

在下面的示例中,如何重新组织array以得到output?这里,所有共享相同name键的对象中的value键(无论是数组还是其他类型)都将被合并。

var array = [
    {
        name: "foo1",
        value: "val1"
    }, {
        name: "foo1",
        value: [
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: "val4"
    }
];

var output = [
    {
        name: "foo1",
        value: [
            "val1",
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: [
            "val4"
        ]
    }
];

1
数组 array 是否会变成 {name:"foo1",value:["val1"]} - BenG
@BG101 不是数组,但如果这有帮助的话,我可以事先制作它。 - zdebruine
1
你尝试过使用jQuery的extend()方法吗?或者你正在尝试编写自己的合并脚本吗?那么可能会像这样:var newArray = $.extend({}, array, output); - user3186219
1
您是否有特定的内容合并方式,或者$.extend()就可以胜任? - user3186219
@ZachPerkitny 我不确定 $.extend() 是否足够,因为我必须处理键值内的数组。 - zdebruine
请给我一个示例,说明您希望如何合并它。 - user3186219
16个回答

75

这是一个选项:

var array = [{
  name: "foo1",
  value: "val1"
}, {
  name: "foo1",
  value: ["val2", "val3"]
}, {
  name: "foo2",
  value: "val4"
}];

var output = [];

array.forEach(function(item) {
  var existing = output.filter(function(v, i) {
    return v.name == item.name;
  });
  if (existing.length) {
    var existingIndex = output.indexOf(existing[0]);
    output[existingIndex].value = output[existingIndex].value.concat(item.value);
  } else {
    if (typeof item.value == 'string')
      item.value = [item.value];
    output.push(item);
  }
});

console.dir(output);


3
我的 keyvalue 名称不同。我感到困惑,因为输入对象中的 value 名称与您在 forEach 循环中使用的参数名称也被命名为 value。请帮我消除困惑。谢谢。 - Arpit Kumar
抱歉 @ArpitMeena,现在才看到您的评论请求。我已将其更改为“item”,以帮助以后使用。 - BenG
如果我有一个包含多个对象的数组,它们都共享同一个未知键,例如其中一个对象结构是{ "127.0.0.1" : 1, "127.0.0.2" : 1, "127.0.0.3" : 1, "127.0.0.4" : 1, },第二个对象是{ "127.0.0.1" : 1000, "127.0.0.2" : 1000, "127.0.0.3" : 1000, "127.0.0.4" : 1000, },如何合并它们,使输出结果类似于{"127.0.0.1": 1, "my-custom-key": 1000}等。注意:值可能不同,我只是举了一个简单的例子。 - sasha romanov

20

Here is another way of achieving that goal:

var array = [{
  name: "foo1",
  value: "val1"
}, {
  name: "foo1",
  value: [
    "val2",
    "val3"
  ]
}, {
  name: "foo2",
  value: "val4"
}];

var output = array.reduce(function(o, cur) {

  // Get the index of the key-value pair.
  var occurs = o.reduce(function(n, item, i) {
    return (item.name === cur.name) ? i : n;
  }, -1);

  // If the name is found,
  if (occurs >= 0) {

    // append the current value to its list of values.
    o[occurs].value = o[occurs].value.concat(cur.value);

    // Otherwise,
  } else {

    // add the current item to o (but make sure the value is an array).
    var obj = {
      name: cur.name,
      value: [cur.value]
    };
    o = o.concat([obj]);
  }

  return o;
}, []);

console.log(output);


如果我需要将值作为对象添加,该怎么办? - Swapnil Sudhir

15

var arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];

const result = arrays.reduce((acc, {name, value}) => {
  acc[name] ??= {name: name, value: []};
  if(Array.isArray(value)) // if it's array type then concat 
    acc[name].value = acc[name].value.concat(value);
  else
    acc[name].value.push(value);
  
  return acc;
}, {});

console.log(Object.values(result));


2
你好,新手上路。??=是什么意思? - Mekel Ilyasa
3
嗨@MekelIlyasa,它的意思是“逻辑空值赋值”。它等同于检查条件if(!acc[name]) acc[name] = {name: name, value: []} - Nguyễn Văn Phong

11

使用lodash

var array = [{name:"foo1",value:"val1"},{name:"foo1",value:["val2","val3"]},{name:"foo2",value:"val4"}];

function mergeNames (arr) {
    return _.chain(arr).groupBy('name').mapValues(function (v) {
        return _.chain(v).pluck('value').flattenDeep();
    }).value();
}

console.log(mergeNames(array));

3
合并姓名函数应该是这样的:const mergeNames = arr => _.chain(arr).groupBy('name').mapValues(v => _.chain(v).map('value').flattenDeep().value()).value();。然后通过 console.log(mergeNames(array)); 来调用该函数。 - Softmixt
数据正在到来'2021-04-14': LodashWrapper { wrapped: LazyWrapper { wrapped: [LodashWrapper], actions: [Array], dir: 1, filtered: false, iteratees: [Array], takeCount: 4294967295, views: [] }, actions: [ [Object] ], chain: true, index: 0, values: undefined }如何将其转换为数组格式? - Swapnil Sudhir

9

这里是使用ES6 Map的一种版本:

const arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];

const map = new Map(arrays.map(({name, value}) => [name, { name, value: [] }])); 
for (let {name, value} of arrays) map.get(name).value.push(...[value].flat());
console.log([...map.values()]);


4

虽然这个问题已经被问了一段时间,但我也想加入讨论。对于像这样需要重复执行基本功能的函数,如果可以的话,我更喜欢避免编写较长的函数和循环,并将函数开发为使用浅层Array.prototype函数的单行代码,例如.map(),以及其他一些ES6+好用的工具,如Object.entries()Object.fromEntries()。将所有这些组合在一起,我们可以相对轻松地执行此类函数。

首先,我将作为rest参数传递给函数的任意数量的对象与一个我们将用来收集所有键和值的空对象结合起来。

[{}, ...objs]

接下来,我将使用.map()数组原型函数与Object.entries()结合使用,循环遍历每个对象的所有条目以及它们包含的任何子数组元素,然后,如果尚未声明空对象的键,则将其设置为该值,否则,如果已经声明了对象键,则将新值推送到该键。
[{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]

最后,为了将任何单元素数组替换为其包含的值,我在结果数组上再次运行 .map() 函数,同时使用 Object.entries()Object.fromEntries(),类似于之前的操作。

let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));

这将为您留下最终合并的对象,与您所规定的完全一致。

let a = {
  a: [1,9],
  b: 1,
  c: 1
}
let b = {
  a: 2,
  b: 2
}
let c = {
  b: 3,
  c: 3,
  d: 5
}

let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));

getMergedObjs(a,b,c); // { a: [ 1, 9, 2 ], b: [ 1, 2, 3 ], c: [ 1, 3 ], d: 5 }


4
使用lodash 的 "uniqWith"。如下所示。
let _ = require("lodash");

var array = [
  { name: "foo1", value: "1" },
  { name: "foo1", value: "2" },
  { name: "foo2", value: "3" },
  { name: "foo1", value: "4" }
];

let merged = _.uniqWith(array, (pre, cur) => {
  if (pre.name == cur.name) {
    cur.value = cur.value + "," + pre.value;
    return true;
  }
  return false;
});

console.log(merged);
// output:  [{ name: "foo1", value: "1,2,4" }, { name: "foo2", value: "3" }];



我该如何实现这个目标,让foo1的值加起来是7呢?(1+2+4) - Aaron

4
使用 reduce 函数:
var mergedObj = array.reduce((acc, obj) => {
    if (acc[obj.name]) {
       acc[obj.name].value = acc[obj.name].value.isArray ? 
       acc[obj.name].value.concat(obj.value) : 
       [acc[obj.name].value].concat(obj.value);

    } else {
      acc[obj.name] = obj;
    }

    return acc;
}, {});

let output = [];
for (let prop in mergedObj) {
  output.push(mergedObj[prop]) 
}

3

试试这个:

var array = [{name:"foo1",value:"val1"},{name:"foo1",value:["val2","val3"]},{name:"foo2",value:"val4"},{name:"foo2",value:"val5"}];

for(var j=0;j<array.length;j++){
  var current = array[j];
  for(var i=j+1;i<array.length;i++){
    if(current.name = array[i].name){
      if(!isArray(current.value))
        current.value = [ current.value ];
      if(isArray(array[i].value))
         for(var v=0;v<array[i].value.length;v++)
           current.value.push(array[i].value[v]);
      else
        current.value.push(array[i].value);
      array.splice(i,1);
      i++;
    }
  }
}

function isArray(myArray) {
    return myArray.constructor.toString().indexOf("Array") > -1;
}

document.write(JSON.stringify(array));


这虽然是可靠的,但我正试图避开传统的暴力破解方法。 - zdebruine
1
是的,但我没有使用“无限”循环:-),我只使用了两个循环,更重要的是,我直接在array变量上进行操作,消除了对另一个output变量的需求,使其更节省内存。不过,我也喜欢BG101和Hunan的两个答案,所以我给他们点了赞。 - Racil Hilan

3
这项工作也可以!

      var array = [
        {
          name: "foo1",
          value: "val1",
        },
        {
          name: "foo1",
          value: ["val2", "val3"],
        },
        {
          name: "foo2",
          value: "val4",
        },
      ];
      let arr2 = [];
      array.forEach((element) => { // remove duplicate name
        let match = arr2.find((r) => r.name == element.name);
        if (match) {
        } else {
          arr2.push({ name: element.name, value: [] });
        }
      });
      arr2.map((item) => {
        array.map((e) => {
          if (e.name == item.name) {
            if (typeof e.value == "object") { //lets map if value is an object
              e.value.map((z) => {
                item.value.push(z);
              });
            } else {
              item.value.push(e.value);
            }
          }
        });
      });
      console.log(arr2);


你如何将foo1的值相加?基本上将val1,val2和val3相加为一个值? - Mars2024
我正在使用临时数组arr2来保存值,然后再次映射以查看是否为对象,然后在其上创建一个数组,如果不是对象/数组,则将该值推送到其中。@jedLancer - qahtan said

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