34

Trying to see if there is any javascript library functionality that can merge the values for a specific key of two json objects

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

using $.extend and $.merge giving below results

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

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

what I am looking for is ‍{ "student-marks":{'math':1,'physics':5, 'chemistry':3,'history':2} }‍

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Venkat
  • 1,229
  • 1
  • 15
  • 23
  • 2
    Just perform it **manually** using loops. Not everything in our everyday job can be solved in one built-in function call – zerkms Aug 28 '13 at 21:50
  • 5
    @zerkms, this one can be, though :S – Mulan Aug 28 '13 at 21:56
  • @naomik: it can be, as well as ton of other things. A person cannot add 2 numbers and asking about solving integrals. That doesn't sound right imho. – zerkms Aug 28 '13 at 22:17
  • @zerkms, huh? I don't understand the analogy. – Mulan Aug 28 '13 at 22:23
  • 2
    @naomik: my point was: this task can be solved manually by merging items one by one in a `for` loop. But OP doesn't know how to do that. Basically he doesn't know trivial things and treats programming as a kind of magic with a ready to use recipe for every single problem. Which is often not the case. I agree a lot of things can be done using libraries' function, but at first place you need to know what and how happens under the hood. – zerkms Aug 28 '13 at 22:37
  • 18
    That's quite a number of assumptions you've made there. – Mulan Aug 28 '13 at 23:51
  • 5
    @zerkms I'd agree with naomik. The question is actually pretty straightforward. Somebody is asking for an assist on achieving something in JS via built in language functionality... which is simply good programming. Sure, he may not know how to do it himself, but that's part of the journey. Or in your book, are APIs "magic", too, that should be avoided? Instead of insulting a newcomer, you could help, or make no comment at all. Basic SO guidelines. – dudewad Apr 07 '15 at 19:33
  • 3
    @Venkat a quick note- you should be using double quotes in your JSON, not single quotes. It's part of the JSON spec that double quotes are to be used to be considered "valid". – dudewad Apr 07 '15 at 19:35

5 Answers5

40

You want a deep extend

$.extend(true, {}, x, y);

See the docs for jQuery.extend([deep], target, object1[, objectN])

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 2
    What would the similar solution be with lodash or underscore? I have nearly identical problem, but an not using jQuery and would prefer lodash implementation of _.merge perhaps? Here is my Q: http://stackoverflow.com/questions/23186187/merge-two-javascript-objects-if-key-value-is-equal – DeBraid Apr 20 '14 at 18:36
12

The simple javascript function without depending jQuery will help you to merge two JSON object which has nested objects.

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;
}
shibualexis
  • 4,534
  • 3
  • 20
  • 25
  • 3
    This solution doesn't work in NodeJS. Had to install https://www.npmjs.com/package/extend. – tim-montague Jan 15 '16 at 01:35
  • This script works for simple objects. To make it work with nested objects I had to change the script slightly. To make it work with arrays within objects requires another code block. When I get it right I'll post it here. – andrew lorien Feb 11 '17 at 07:30
  • 2
    Still waitin' on ya @andrewlorien – Leo Nov 30 '17 at 01:32
5

The new spread operator from the ES2018 provides some nice way to do this:

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}
));

This was the best recursive solution that I could get

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) );
Thiago Mata
  • 2,825
  • 33
  • 32
-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>
-1

A bit less verbose.

answer = { "student-marks": Object.assign(x['student-marks'], y['student-marks']) }
  • warning this has side-effects on x.
toonsend
  • 1,296
  • 13
  • 16