7

I'm thinking this must be a common problem but can't seem to find the solution. Using JSON config files to extend a jQuery object that contains objects and arrays.

For the objects and simple properties, I want to overwrite (as extend does nicely).

For the arrays there may or may not be existing items.

Currently an array just overwrites the first elements

var sourceObj = {propterty:"change Me",anArray:[{name:"first"},{name:"second"}]},
    configJSON = '{"propterty":"New Val","anArray":[{"name":"third"}]}',
    configObj = JSON.parse(configJSON);

$.extend(true,sourceObj,configObj);

http://jsfiddle.net/PmuwV/

This returns:

{propterty:"New Val" , anArray:[{name:"third"},{name:"second"}}

Can I instead get:

{propterty:"New Val",anArray:[{name:"first"},{name:"second"},{name:"third"}]}

while ALSO allowing for updating "first" and "second" objects?

"anArray":[{"name":"second","newProp":"add newProp to second"}]

Could/should extend be modified to compare array items and extend or add based on some rule or set property value such as "name"?

Thanks for any advice or pointers.

8
  • This seems to be the the closest i've found that might do it - stackoverflow.com/questions/3748697/… Commented Nov 7, 2012 at 23:47
  • The normal use of $.extend is for merging user-supplied options with defaults. In this case, it's usually best for a new property to completely replace the one in the original object; appending array properties doesn't fit in with the way it's typically used. What do you need this for? Commented Nov 8, 2012 at 0:00
  • @Barmar true that is one use case, but there are many others...including extending new functions/methods to jQuery object itself Commented Nov 8, 2012 at 0:27
  • 1
    From $.extend docs :The merge performed by $.extend() is not recursive by default; if a property of the first object is itself an object or array, it will be completely overwritten by a property with the same key in the second object. api.jquery.com/jQuery.extend Commented Nov 8, 2012 at 0:29
  • 1
    @charlietfl He knows that -- he's asking whether it would be reasonable to change that definition. Commented Nov 8, 2012 at 0:30

4 Answers 4

2

I used this solution http://jsfiddle.net/PmuwV/2/ modified from How can I merge properties of two JavaScript objects dynamically? also from JavaScript equivalent of jQuery's extend method

requires isDOMNode() I just added in a jquery merge (yes I feel dirty too) on arrays in which duplicates will need to be cleaned up post merge. The Jquery source for extend does something very similar but i found this to be more readable.

function mergeRecursive() {
  // _mergeRecursive does the actual job with two arguments.
  var _mergeRecursive = function (dst, src) {
    if ( isDOMNode(src) || typeof src!=='object' || src===null) {
      return dst; 
    }

    for ( var p in src ) {

//my added bit here - [SB]
      if ($.isArray(src[p])){
          $.merge(dst[p],src[p]);
          var dupes = {},
               singles = [];
          $.each(  dst[p], function(i, el) {
             if ((dupes[el.name] > -1) &&  (el.name)) {
                 $.extend(singles[dupes[el.name]],el);
             }else{
                  if (el.name ){
                     dupes[el.name] = i;
                  }
                 singles.push(el);
             }
         });
         dst[p] = singles;
         }
         continue;        
      }
//the rest is original - [SB]

      if( !src.hasOwnProperty(p) ) continue;
      if ( src[p]===undefined ) continue;
      if ( typeof src[p]!=='object' || src[p]===null) {
        dst[p] = src[p];
      } else if ( typeof dst[p]!=='object' || dst[p]===null ) {
        dst[p] = _mergeRecursive(src[p].constructor===Array ? [] : {}, src[p]); 
      } else {              
        _mergeRecursive(dst[p], src[p]);
      }
    }
    return dst;
  }

  // Loop through arguments and merge them into the first argument. 
  var out = arguments[0];
  if ( typeof out!=='object' || out===null) return out;
  for ( var i=1, il=arguments.length; i<il; i++ ) {
    _mergeRecursive(out, arguments[i]);
  }
  return out;
}
Sign up to request clarification or add additional context in comments.

3 Comments

updated at jsfiddle.net/PmuwV/5 to merge duplicate array objects with a particular matching property
Needed something similar so I modified yours a bit to use underscore: codepen.io/clouddueling/pen/nFDCH
Nice solution but I found 2 bugs found in the above code, so I added an 'Answer' with the updated code.
2

It's quite straightforward with lodash library

var targetObj = {
    customerId: "123", 
    orders: [
        "item1", "item2"
    ]
};

var otherObj = {
    customerName: "John", 
    orders: [
        "item3", "item4"
    ]
};

_.merge(targetObj, otherObj, function (a, b) {
  if (_.isArray(a)) {
    return a.concat(b);
  }
});

Result is:

targetObj = {
    customerId: "123",
    customerName: "John",
    orders: [
        "item1", "item2", "item3", "item4"
    ]       
}

1 Comment

For anyone reading this in 2016 or after, it seems that the correct lodash function for the example above today is _.mergeWith()
0

I know this is old, but I found this post and used Steve Blacks code above but found a few bugs :

  1. Theres an extra '}' before the 'continue' in the isArray section.
  2. If the source did not have the array at all, it would throw an error, so I added this into the isArray section

    if ( !dst[p] ) {
        dst[p] = src[p];
        continue;
    }
    

So the finished code looks like this :

function isDOMNode(v) {
    if ( v===null ) return false;
    if ( typeof v!=='object' ) return false;
    if ( !('nodeName' in v) ) return false; 
    var nn = v.nodeName;
    try {
      v.nodeName = 'is readonly?';
    } catch (e) {
      return true;
    }
    if ( v.nodeName===nn ) return true;
    v.nodeName = nn;
    return false;
}

function mergeRecursive() {
    // _mergeRecursive does the actual job with two arguments.
    var _mergeRecursive = function (dst, src) {
        if ( isDOMNode(src) || typeof src!=='object' || src===null) {
            return dst; 
        }

        for ( var p in src ) {
            if ($.isArray(src[p])) {
                if ( !dst[p] ) {
                    dst[p] = src[p];
                    continue;
                }
                $.merge(dst[p],src[p]);
                var dupes = {}, singles = [];
                $.each( dst[p], function(i, el) {
                    if ((dupes[el.name] > -1) &&  (el.name)) {
                        $.extend(singles[dupes[el.name]],el);
                    } else {
                        if (el.name) {
                            dupes[el.name] = i;
                        }
                     singles.push(el);
                   }
                });
                dst[p] = singles;
                continue;        
            }

            if ( !src.hasOwnProperty(p) ) continue;
            if ( src[p]===undefined )     continue;
            if ( typeof src[p]!=='object' || src[p]===null) {
                dst[p] = src[p];
            } else if ( typeof dst[p]!=='object' || dst[p]===null ) {
                dst[p] = _mergeRecursive(src[p].constructor===Array ? [] : {}, src[p]); 
            } else {              
                _mergeRecursive(dst[p], src[p]);
            }
        }
        return dst;
    }

    // Loop through arguments and merge them into the first argument. 
    var out = arguments[0];
    if ( typeof out!=='object' || out===null) return out;
    for ( var i=1, il=arguments.length; i<il; i++ ) {
      _mergeRecursive(out, arguments[i]);
    }
    return out;
}

Comments

0
<html>
<head>
<script type="text/javascript" src="./jquery-2.1.3.js"></script> <!-- for json extend / merge -->
<script type="text/javascript" src="./jQuery.extendext.min.js"></script> <!-- for json extend / merge - with array extend (instead of array overwrite) - https://github.com/mistic100/jQuery.extendext -->

<script>
var jsonResult = {};
var json1 =
{
    "properties":
    {
        "street_address": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" }
    },
    "required": ["street_address", "city", "state"]
};
var json2 =
{
    "properties":
    {
        "country": { "type": "string" },
        "country-dial-code": { "type": "integer" },
        "country-short": { "type": "string" }
    },
    "required": ["country", "country-dial-code", "country-short"]
};
$.extendext(true, 'extend', jsonResult, json1, json2);
console.log(JSON.stringify(jsonResult));
/* output ->
{   "properties":
    {   "street_address":{"type":"string"},
        "city":{"type":"string"},
        "state":{"type":"string"},
        "country":{"type":"string"},
        "country-dial-code":{"type":"integer"},
        "country-short":{"type":"string"}
    },
    "required":["street_address","city","state","country","country-dial-code","country-short"]
}
*/
</script>
</head>
<body>
</body>
</html>

1 Comment

key "F12" to view console

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.