如何按键深度合并两个对象的值

34

我正在尝试查找一些JavaScript库功能,可以合并两个JSON对象中特定键的值。

var x ={ "student-marks":{'math':1,'physics':5} };
var y ={ "student-marks":{'chemistry':3,'history':2} };

使用$.extend和$.merge会得到以下结果

$.extend({},x,y)  leads to  { "student-marks":{'chemistry':3,'history':2} }

$.merge(x,y) leads to { "student-marks":{'math':1,'physics':2} }

我要寻找的是 ‍{ "student-marks":{'math':1,'physics':5, 'chemistry':3,'history':2} }‍


5
@zerkms,这个可以,但是 :S - Mulan
2
@naomik:我的观点是:这个任务可以通过在“for”循环中逐个合并项目来手动解决。但OP不知道如何做到这一点。基本上,他不知道微不足道的事情,并将编程视为一种魔法,每个问题都有一个现成的配方。这通常不是这种情况。我同意很多事情可以使用库函数完成,但首先你需要知道发生了什么以及如何发生。 - zerkms
18
你做出了相当多的假设。 - Mulan
5
我同意naomik的观点。这个问题实际上非常简单明了。有人正在寻求通过JavaScript内置的语言功能来帮助实现某些事情......这只是良好的编程习惯。当然,他可能不知道如何自己做到这一点,但这就是旅程的一部分。在你看来,API也是“魔法”吗?或者应该避免使用它们?与其侮辱新手,你可以提供帮助或完全不发表评论。这是基本的SO指导方针。 - dudewad
3
@Venkat,简短说明一下:你应该在你的JSON中使用双引号,而不是单引号。这是JSON规范的一部分,只有使用双引号才被认为是“有效”的。 - dudewad
显示剩余3条评论
5个回答

40

2
使用lodash或underscore会有类似的解决方案吗?我有一个几乎相同的问题,但没有使用jQuery,并且可能更喜欢使用lodash库中的_.merge实现?这是我的问题链接:http://stackoverflow.com/questions/23186187/merge-two-javascript-objects-if-key-value-is-equal - DeBraid

12

这个简单的JavaScript函数不依赖于jQuery,它可以帮助您合并两个具有嵌套对象的JSON对象。

function mergeJSON(source1,source2){
    /*
     * Properties from the Souce1 object will be copied to Source2 Object.
     * Note: This method will return a new merged object, Source1 and Source2 original values will not be replaced.
     * */
    var mergedJSON = Object.create(source2);// Copying Source2 to a new Object

    for (var attrname in source1) {
        if(mergedJSON.hasOwnProperty(attrname)) {
          if ( source1[attrname]!=null && source1[attrname].constructor==Object ) {
              /*
               * Recursive call if the property is an object,
               * Iterate the object and set all properties of the inner object.
              */
              mergedJSON[attrname] = mergeJSON(source1[attrname], mergedJSON[attrname]);
          } 

        } else {//else copy the property from source1
            mergedJSON[attrname] = source1[attrname];

        }
      }

      return mergedJSON;
}

3
这个解决方案在 NodeJS 中不起作用。必须安装 https://www.npmjs.com/package/extend。 - tim-montague
这个脚本适用于简单的对象。为了使它能够处理嵌套对象,我不得不稍微修改一下脚本。要使其能够处理对象内的数组,需要另一个代码块。等我弄对了,我会在这里发布的。 - andrew lorien
2
仍在等待您的回复 @andrewlorien - Leo

5
ES2018的新扩展运算符提供了一些不错的方式来实现这一点:

function combine(...list){
  return list.reduce(
     (a,b)=>{
       return {...a,...b}
     }
  )
}

// work as expected with simple objects
console.log(
  combine(
    {x:3,z:1},
    {x:4,m:4},
    {a:1},
    {b:2}
));

// is not a good recursive solution
console.log(
  combine(
    {x:3,z:1,child:{c:1}},
    {x:4,m:4,child:{d:3}},
    {a:1},
    {b:2}
));

这是我能想到的最佳递归解决方案。

function combine_recursive(...list) {    
    return list.reduce(
        (a,b) => {
            // for non objects return b if exists or a
            if ( ! ( a instanceof Object ) || ! ( b instanceof Object ) ) {
                return b !== undefined ? b : a;
            }
            // for objects, get the keys and combine them
            const keys = Object.keys(a).concat(Object.keys(b));
            return keys.map(
                key => { 
                    return  {[key]: combine_recursive(a[key],b[key])}
                }
            ).reduce(
                (x,y) => {
                    return {...x,...y}
                }
            );
        }
    )
}
   
// testing recursive and the priority
console.log(
  combine_recursive(
    {x:3,z:1,child:{c:1,k:1}},
    {x:4,m:4,child:{d:3,k:2}},
    {a:1},
    {b:2}
));
 
// using the example of the question
var x ={ "student-marks":{'math':1,'physics':5} };
var y ={ "student-marks":{'chemistry':3,'history':2} };
console.log( combine_recursive(x,y) );


-1

简洁一点。

answer = { "student-marks": Object.assign(x['student-marks'], y['student-marks']) }
  • 警告:这会对 x 产生副作用。

-1
    https://gist.github.com/samad-aghaei/7250ffb74ed80732debb1cbb14d2bfb0

<pre>
/**
This script can merge two multi dimensional associative array/objects in javascript by comparing given object with its reference and 
will remove additional given keys, adding missed parameteres and also validating values without overhead. Also it will return the default values if no input presented with re-usable reference!
Tested on IE8 and greater.
**/
var module = (function(){
    //To make our reference variable onchangable, have to put it into a function which is fster and more efficient than "JSON.parse(JSON.stringify(VARIABLE))"
    var _defs = function(){
            return {
                   //string, number and boolean are actually regex based validation keys on input values.
                a: ["string", 'Defaul value for "a"'],
                b: ["number", 300],
                c: ["boolean", true],
                d: {
                  da: ["boolean", true],
                  db: ["string", 'Defaul value for "db"'],
                  dc: {
                    dca: ["number", 200],
                    dcb: ["string", 'Default value for "dcb"'],
                    dcc: ["number", 500],
                    dcd: ["boolean", true]
                  },
                  dce: ["string", 'Default value for "dce"'],
                },
                e: ["number", 200],
                f: ["boolean", 0],
                g: ["", 'This is an internal extra parameter']
            }
        }

        var _validation = {
                number: function (defaultValue, userValue) {
                  if(/^[0-9]+$/.test(userValue)) //Only numbers allowed
                    return userValue;
                  else return defaultValue;
                },
                string: function (defaultValue, userValue) {
                  if(/^[a-zA-Z\s]*$/.test(userValue)) //Only A to Z case insentitive with space aloowed.
                    return userValue;
                  else return defaultValue;
                },
                boolean: function (defaultValue, userValue) {
                  if(typeof userValue === 'boolean') //True or False or 0 ,1
                    return userValue;
                  else return defaultValue;
                }
        }

        var _uniqueMerge = function(opts, _ref){
                for(var key in _ref)
                    if (_ref && _ref[key] && _ref[key].constructor && _ref[key].constructor === Object)
                      _ref[key] = _uniqueMerge((opts ? opts[key] : null ), _ref[key] );
                    else if(opts && opts.hasOwnProperty(key))
                      _ref[key] = _validation[_ref[key][0]](_ref[key][1], opts[key]); //or without validation on user enties => ref[key] = obj[key]
                    else _ref[key] = _ref[key][1];
                return _ref;
        }
        var _get = function(inputs){
            return _uniqueMerge(inputs, _defs());
        }
        return {
            options: function(){
            return _get(arguments[0] || null); // for more safety and control on number of input variables! used --> ( arguments[0] || null )
            }
        }
})();


//How to use it:    

input_one = { 
    a : "Hello World", 
  //b : ["number", 400], //User missed this parameter
    c: "Hi",
    d : {
        da : false,
        db : "Hellow! World", // ! is not allowed
        dc : {
            dca : 10,
            dcb : "My String",
            dcc: "3thString",
            dcd : false
      },
      dce: "ANOTHER STRING",
    },
    e: 40,
    f: true,
    z: 'x'
};
console.log( JSON.stringify( module.options(input_one), null ,2 ) );
//Output:
/*
{
  "a": "Hello World",
  "b": 300,
  "c": true,
  "d": {
    "da": false,
    "db": "Defaul value for \"db\"",
    "dc": {
      "dca": 10,
      "dcb": "My String",
      "dcc": 500,
      "dcd": false
    },
    "dce": "ANOTHER STRING"
  },
  "e": 40,
  "f": true,
  "g": "This is an internal extra parameter"
}
*/
input_two = { 
    a : 32,
  //b : ["number", 400], //User missed this parameter
    c: "Hi",
    d : {
        da : false,
        db : "HelloWorld",
        dc : {
            dca : 10,
            dcb : "My String",
            dcd : false
      },
      dce: 73,
    }
};
console.log( JSON.stringify( module.options(input_two), null ,2 ) );
//output
/*
{
  "a": "Defaul value for \"a\"",
  "b": 300,
  "c": true,
  "d": {
    "da": false,
    "db": "HelloWorld",
    "dc": {
      "dca": 10,
      "dcb": "My String",
      "dcc": 500,
      "dcd": false
    },
    "dce": "Default value for \"dce\""
  },
  "e": 200,
  "f": 0,
  "g": "This is an internal extra parameter"
}
*/
//Empty input will return the default values!
console.log( JSON.stringify( module.options(), null ,2 ) );     
//Output
/*  
{
  "a": "Defaul value for \"a\"",
  "b": 300,
  "c": true,
  "d": {
    "da": true,
    "db": "Defaul value for \"db\"",
    "dc": {
      "dca": 200,
      "dcb": "Default value for \"dcb\"",
      "dcc": 500,
      "dcd": true
    },
    "dce": "Default value for \"dce\""
  },
  "e": 200,
  "f": 0,
  "g": "This is an internal extra parameter"
}
*/</pre>

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