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.

Dave L.
  • 9,595
  • 7
  • 43
  • 69
Steve Black
  • 609
  • 5
  • 9
  • This seems to be the the closest i've found that might do it - http://stackoverflow.com/questions/3748697/jquery-extend-array-of-objects – Steve Black Nov 07 '12 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? – Barmar Nov 08 '12 at 00:00
  • @Barmar true that is one use case, but there are many others...including extending new functions/methods to jQuery object itself – charlietfl Nov 08 '12 at 00: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.` http://api.jquery.com/jQuery.extend/ – charlietfl Nov 08 '12 at 00:29
  • @charlietfl Would those uses require appending array properties like this? – Barmar Nov 08 '12 at 00:29
  • 1
    @charlietfl He knows that -- he's asking whether it would be reasonable to change that definition. – Barmar Nov 08 '12 at 00:30
  • would need to loop over the object and map new array – charlietfl Nov 08 '12 at 00:31
  • Thanks to you both. I'm getting this config to extend the display of a form from another system. The JSON config format needs to be pretty simple and consistent as we are dealing with a large number of forms. Jquery.Extend does almost exactly what I want but just not with arrays (as documented). So could/should I "extend" Extend or essentially rebuild this functionality? – Steve Black Nov 08 '12 at 01:00

4 Answers4

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;
}
Community
  • 1
  • 1
Steve Black
  • 609
  • 5
  • 9
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"
    ]       
}
Enis
  • 186
  • 1
  • 4
  • 1
    For anyone reading this in 2016 or after, it seems that the correct lodash function for the example above today is `_.mergeWith()` – AsGoodAsItGets Aug 25 '16 at 08:42
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;
}
user3708842
  • 543
  • 8
  • 23
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>
Andre Nel
  • 432
  • 2
  • 9