HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptMinor

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

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
arraysobjectsmergeincludetwothatoptimizehow

Problem

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: https://stackoverflow.com/questions/171251/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

  • an array of simple values will look for values in the new object and add those to the end of the existing object and



  • 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 = {}

Solution

I think the following is doing the same thing, you'll need to check it. It should be pretty fast, it just references objects that don't exist on the target. I changed names from def and obj to target and source because they make more sense to me.

I also included as simple isArray function. Note that I don't like using the constructor property as it's public and you might get an own property rather than the inherited one, so I changed the test for object.

If you test of an object, then for an array, what falls through should be a primitve. There are a lot of assumptions here, I'd implement a bit more checking to make sure I was getting what I expected.

function isArray(o) {
  return Object.prototype.toString.call(o) == "[object Array]";
}

// Assumes that target and source are either objects (Object or Array) or undefined
// Since will be used to convert to JSON, just reference objects where possible
function mergeObjects(target, source) {

  var item, tItem, o, idx;

  // If either argument is undefined, return the other.
  // If both are undefined, return undefined.
  if (typeof source == 'undefined') {
    return source;
  } else if (typeof target == 'undefined') {
    return target;
  }

  // Assume both are objects and don't care about inherited properties
  for (var prop in source) {
    item = source[prop];

    if (typeof item == 'object' && item !== null) {

      if (isArray(item) && item.length) {

        // deal with arrays, will be either array of primitives or array of objects
        // If primitives
        if (typeof item[0] != 'object') {

          // if target doesn't have a similar property, just reference it
          tItem = target[prop];
          if (!tItem) {
            target[prop] = item;

          // Otherwise, copy only those members that don't exist on target
          } else {

            // Create an index of items on target
            o = {};
            for (var i=0, iLen=tItem.length; i<iLen; i++) {
              o[tItem[i]] = true
            }

            // Do check, push missing
            for (var j=0, jLen=item.length; j<jLen; j++) {

              if ( !(item[j] in o) ) {
                tItem.push(item[j]);
              } 
            }
          }
        } else {
          // Deal with array of objects
          // Create index of objects in target object using ID property
          // Assume if target has same named property then it will be similar array
          idx = {};
          tItem = target[prop]

          for (var k=0, kLen=tItem.length; k<kLen; k++) {
            idx[tItem[k].id] = tItem[k];
          }

          // Do updates
          for (var l=0, ll=item.length; l<ll; l++) {
            // If target doesn't have an equivalent, just add it
            if (!(item[l].id in idx)) {
              tItem.push(item[l]);
            } else {
              mergeObjects(idx[item[l].id], item[l]);
            }
          }  
        }
      } else {
        // deal with object
        mergeObjects(target[prop],item);
      }

    } else {
      // item is a primitive, just copy it over
      target[prop] = item;
    }
  }
  return target;
}

Code Snippets

function isArray(o) {
  return Object.prototype.toString.call(o) == "[object Array]";
}

// Assumes that target and source are either objects (Object or Array) or undefined
// Since will be used to convert to JSON, just reference objects where possible
function mergeObjects(target, source) {

  var item, tItem, o, idx;

  // If either argument is undefined, return the other.
  // If both are undefined, return undefined.
  if (typeof source == 'undefined') {
    return source;
  } else if (typeof target == 'undefined') {
    return target;
  }

  // Assume both are objects and don't care about inherited properties
  for (var prop in source) {
    item = source[prop];

    if (typeof item == 'object' && item !== null) {

      if (isArray(item) && item.length) {

        // deal with arrays, will be either array of primitives or array of objects
        // If primitives
        if (typeof item[0] != 'object') {

          // if target doesn't have a similar property, just reference it
          tItem = target[prop];
          if (!tItem) {
            target[prop] = item;

          // Otherwise, copy only those members that don't exist on target
          } else {

            // Create an index of items on target
            o = {};
            for (var i=0, iLen=tItem.length; i<iLen; i++) {
              o[tItem[i]] = true
            }

            // Do check, push missing
            for (var j=0, jLen=item.length; j<jLen; j++) {

              if ( !(item[j] in o) ) {
                tItem.push(item[j]);
              } 
            }
          }
        } else {
          // Deal with array of objects
          // Create index of objects in target object using ID property
          // Assume if target has same named property then it will be similar array
          idx = {};
          tItem = target[prop]

          for (var k=0, kLen=tItem.length; k<kLen; k++) {
            idx[tItem[k].id] = tItem[k];
          }

          // Do updates
          for (var l=0, ll=item.length; l<ll; l++) {
            // If target doesn't have an equivalent, just add it
            if (!(item[l].id in idx)) {
              tItem.push(item[l]);
            } else {
              mergeObjects(idx[item[l].id], item[l]);
            }
          }  
        }
      } else {
        // deal with object
        mergeObjects(target[prop],item);
      }

    } else {
      // item is a primitive, just copy it over
      target[prop] = item;
    }
  }
  return target;
}

Context

StackExchange Code Review Q#16306, answer score: 5

Revisions (0)

No revisions yet.