AlexGad
AlexGad

Reputation: 6692

How to Optimize Merge of Two Objects That Include Arrays of Objects

I need to merge two objects in a code path that is going to be heavily used. The code works, but I am concerned it is not optimized enough for speed and I am looking for any suggestions to improve/replace what I have come up with. I originally started working off an example at the end of this issue: How can I merge properties of two JavaScript objects dynamically?. That solution works well for simple objects. However, my needs have a twist to it which is where the performance concerns come in. I need to be able to support arrays such that

  1. an array of simple values will look for values in the new object and add those to the end of the existing object and
  2. an array of objects will either merge objects (based off existence of an id property) or push new objects (objects whose id property does not exist) to the end of the existing array.

I do not need functions/method cloning and I don't care about hasOwnProperty since the objects go back to JSON strings after merging.

Any suggestions to help me pull every last once of performance from this would be greatly appreciated.

var utils = require("util");

function mergeObjs(def, obj) {
  if (typeof obj == 'undefined') {
    return def;
  } else if (typeof def == 'undefined') {
    return obj;
  }

  for (var i in obj) {
    // if its an object
    if (obj[i] != null && obj[i].constructor == Object)
    {
      def[i] = mergeObjs(def[i], obj[i]);
    }
    // if its an array, simple values need to be joined.  Object values need to be remerged.
    else if(obj[i] != null && utils.isArray(obj[i]) && obj[i].length > 0)
    {
      // test to see if the first element is an object or not so we know the type of array we're dealing with.
      if(obj[i][0].constructor == Object)
      {
        var newobjs = [];
        // create an index of all the existing object IDs for quick access.  There is no way to know how many items will be in the arrays.
        var objids = {}
        for(var x= 0, l= def[i].length ; x < l; x++ )
        {
          objids[def[i][x].id] = x;
        }

        // now walk through the objects in the new array
        // if the ID exists, then merge the objects.
        // if the ID does not exist, push to the end of the def array
        for(var x= 0, l= obj[i].length; x < l; x++)
        {
          var newobj = obj[i][x];
          if(objids[newobj.id] !== undefined)
          {
            def[i][x] = mergeObjs(def[i][x],newobj);
          }
          else {
            newobjs.push(newobj);
          }
        }

        for(var x= 0, l = newobjs.length; x<l; x++) {
          def[i].push(newobjs[x]);
        }
      }
      else {
        for(var x=0; x < obj[i].length; x++)
        {
          var idxObj = obj[i][x];
          if(def[i].indexOf(idxObj) === -1) {
             def[i].push(idxObj);
          }
        }
      }
    }
    else
    {
      def[i] = obj[i];
    }
  }
  return def;}

The object samples to merge:

var obj1 = {
            "name" : "myname",
            "status" : 0,
            "profile": { "sex":"m", "isactive" : true},
            "strarr":["one", "three"],
            "objarray": [
                {
                    "id": 1,
                    "email": "[email protected]",
                    "isactive":true
                },
                {
                    "id": 2,
                    "email": "[email protected]",
                    "isactive":false
                }
            ]
        };
        var obj2 = {
            "name" : "myname",
            "status" : 1,
            "newfield": 1,
            "profile": { "isactive" : false,  "city": "new York"},
            "strarr":["two"],
            "objarray": [
                {
                    "id": 1,
                    "isactive":false
                },
                {
                    "id": 2,
                    "email": "[email protected]"
                },
                {
                    "id": 3,
                    "email": "[email protected]",
                    "isactive" : true
                }
            ]
        };

Once merged, this console.log(mergeObjs(obj1, obj2)) should produce this:

{ name: 'myname',
  status: 1,
  profile: { sex: 'm', isactive: false, city: 'new York' },
  strarr: [ 'one', 'three', 'two' ],
  objarray: 
   [ { id: 1, email: '[email protected]', isactive: false },
     { id: 2, email: '[email protected]', isactive: false },
     { id: 3, email: '[email protected]', isactive: true } ],
  newfield: 1 }

Upvotes: 3

Views: 3034

Answers (2)

Josh
Josh

Reputation: 12722

I'd check out: https://github.com/bestiejs/lodash

_.merge is not on the list of 'optimized' functions, but this is a battle tested, battle hardened. He also has a performance suite, could ask how you might contribute to the perf suite to get some visibility into the merge implementation.

https://github.com/bestiejs/lodash/blob/master/lodash.js#L1677-1738

Edit: As an aside, I wouldn't prematurely optimize. I would see if this is actually a problem in your use case and then move on to actual data. I would look at something like: https://github.com/felixge/faster-than-c

Basic tenets:

  • Collect data
  • Analyze it
  • Find problems
  • Fix them
  • Repeat

He's got tips on each of those.

Upvotes: 3

deepwell
deepwell

Reputation: 20871

If you don't use Lo-Dash, and just want a tool to merge two objects including their arrays, use deepmerge: https://github.com/nrf110/deepmerge

npm install deepmerge

Upvotes: 1

Related Questions