Deep Copy vs JSON Stringify / JSON Parse

JavaScript performance comparison

Revision 26 of this test case created by

Preparation code

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.4.0/lodash.js"></script>

      
<script>
Benchmark.prototype.setup = function() {
  function googTypeOf(value) {
    var s = typeof value;
    if (s == 'object') {
      if (value) {
        // Check these first, so we can avoid calling Object.prototype.toString if
        // possible.
        //
        // IE improperly marshals tyepof across execution contexts, but a
        // cross-context object will still return false for "instanceof Object".
        if (value instanceof Array) {
          return 'array';
        } else if (value instanceof Object) {
          return s;
        }
  
        // HACK: In order to use an Object prototype method on the arbitrary
        //   value, the compiler requires the value be cast to type Object,
        //   even though the ECMA spec explicitly allows it.
        var className = Object.prototype.toString.call(
            /** @type {Object} */ (value));
        // In Firefox 3.6, attempting to access iframe window objects' length
        // property throws an NS_ERROR_FAILURE, so we need to special-case it
        // here.
        if (className == '[object Window]') {
          return 'object';
        }
  
        // We cannot always use constructor == Array or instanceof Array because
        // different frames have different Array objects. In IE6, if the iframe
        // where the array was created is destroyed, the array loses its
        // prototype. Then dereferencing val.splice here throws an exception, so
        // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
        // so that will work. In this case, this function will return false and
        // most array functions will still work because the array is still
        // array-like (supports length and []) even though it has lost its
        // prototype.
        // Mark Miller noticed that Object.prototype.toString
        // allows access to the unforgeable [[Class]] property.
        //  15.2.4.2 Object.prototype.toString ( )
        //  When the toString method is called, the following steps are taken:
        //      1. Get the [[Class]] property of this object.
        //      2. Compute a string value by concatenating the three strings
        //         "[object ", Result(1), and "]".
        //      3. Return Result(2).
        // and this behavior survives the destruction of the execution context.
        if ((className == '[object Array]' ||
             // In IE all non value types are wrapped as objects across window
             // boundaries (not iframe though) so we have to do object detection
             // for this edge case.
             typeof value.length == 'number' &&
             typeof value.splice != 'undefined' &&
             typeof value.propertyIsEnumerable != 'undefined' &&
             !value.propertyIsEnumerable('splice')
  
            )) {
          return 'array';
        }
        // HACK: There is still an array case that fails.
        //     function ArrayImpostor() {}
        //     ArrayImpostor.prototype = [];
        //     var impostor = new ArrayImpostor;
        // this can be fixed by getting rid of the fast path
        // (value instanceof Array) and solely relying on
        // (value && Object.prototype.toString.vall(value) === '[object Array]')
        // but that would require many more function calls and is not warranted
        // unless closure code is receiving objects from untrusted sources.
  
        // IE in cross-window calls does not correctly marshal the function type
        // (it appears just as an object) so we cannot use just typeof val ==
        // 'function'. However, if the object has a call property, it is a
        // function.
        if ((className == '[object Function]' ||
            typeof value.call != 'undefined' &&
            typeof value.propertyIsEnumerable != 'undefined' &&
            !value.propertyIsEnumerable('call'))) {
          return 'function';
        }
  
      } else {
        return 'null';
      }
  
    } else if (s == 'function' && typeof value.call == 'undefined') {
      // In Safari typeof nodeList returns 'function', and on Firefox typeof
      // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
      // would like to return object for those and we can detect an invalid
      // function by making sure that the function object has a call method.
      return 'object';
    }
    return s;
  };
  
  function googIsFunction(val) {
    return googTypeOf(val) == 'function';
  };
  
  function unsafeClone(obj) {
    var type = googTypeOf(obj);
    if (type == 'object' || type == 'array') {
      if (googIsFunction(obj.clone)) {
        return obj.clone();
      }
      var clone = type == 'array' ? [] : {};
      for (var key in obj) {
        clone[key] = unsafeClone(obj[key]);
      }
      return clone;
    }
  
    return obj;
  };
  
  function recursiveDeepCopy(o) {
    var newO,
      i;
  
    if (typeof o !== 'object') {
      return o;
    }
    if (!o) {
      return o;
    }
  
    if ('[object Array]' === Object.prototype.toString.apply(o)) {
      newO = [];
      for (i = 0; i < o.length; i += 1) {
        newO[i] = recursiveDeepCopy(o[i]);
      }
      return newO;
    }
  
    newO = {};
    for (i in o) {
      if (o.hasOwnProperty(i)) {
        newO[i] = recursiveDeepCopy(o[i]);
      }
    }
    return newO;
  }
  
  function recursiveDeepCopyNoTouch(o) {
    var i;
  
    if (typeof o !== 'object') {
      return o;
    }
    if (!o) {
      return o;
    }
  
    if ('[object Array]' === Object.prototype.toString.apply(o)) {
      for (i = 0; i < o.length; i += 1) {
        recursiveDeepCopy(o[i]);
      }
      return o;
    }
  
    for (i in o) {
      if (o.hasOwnProperty(i)) {
        recursiveDeepCopy(o[i]);
      }
    }
    return o;
  }
  
  function deepCopy(o) {
    var copy = o,
      k;
  
    if (o && typeof o === 'object') {
      copy = Object.prototype.toString.call(o) === '[object Array]' ? [] : {};
      for (k in o) {
        copy[k] = deepCopy(o[k]);
      }
    }
  
    return copy;
  }
  
  function clone(obj) {
    if (obj == null || typeof(obj) != 'object')
      return obj;
  
    var temp = new obj.constructor();
    for (var key in obj)
      temp[key] = clone(obj[key]);
  
    return temp;
  }
  
  function createUC() { return {
    "list": {
      "0oVwOM": {
        "id": "0oVwOM",
        "parent": "pTlmbh",
        "name": "New node",
        "created_at": 1384289621
      },
      "aHxe8k": {
        "id": "aHxe8k",
        "parent": "Fhs2hL",
        "name": "hjkhjkhjk",
        "created_at": 1384354593
      },
      "Fhs2hL": {
        "id": "Fhs2hL",
        "parent": "root",
        "name": "test",
        "created_at": 1383403881
      },
      "HYPSgv": {
        "id": "HYPSgv",
        "parent": "0oVwOM",
        "name": "New node",
        "created_at": 1384342657
      },
      "osFIMf": {
        "id": "osFIMf",
        "parent": "root",
        "name": "New node",
        "created_at": 1384354584
      },
      "PsovXE": {
        "id": "PsovXE",
        "parent": "root",
        "name": "hjkhjkhjk",
        "created_at": 1384354589
      },
      "pTlmbh": {
        "id": "pTlmbh",
        "parent": "Fhs2hL",
        "name": "New node",
        "created_at": 1384289277
      },
      "RbXhdJ": {
        "id": "RbXhdJ",
        "parent": "root",
        "name": "empty",
        "created_at": 1384359806
      }
    },
    "maps": {
      "parent": {
        "pTlmbh": {
          "0oVwOM": {
            "id": "0oVwOM",
            "parent": "pTlmbh",
            "name": "New node",
            "created_at": 1384289621
          }
        },
        "Fhs2hL": {
          "aHxe8k": {
            "id": "aHxe8k",
            "parent": "Fhs2hL",
            "name": "hjkhjkhjk",
            "created_at": 1384354593
          },
          "pTlmbh": {
            "id": "pTlmbh",
            "parent": "Fhs2hL",
            "name": "New node",
            "created_at": 1384289277
          }
        },
        "root": {
          "Fhs2hL": {
            "id": "Fhs2hL",
            "parent": "root",
            "name": "test",
            "created_at": 1383403881
          },
          "osFIMf": {
            "id": "osFIMf",
            "parent": "root",
            "name": "New node",
            "created_at": 1384354584
          },
          "PsovXE": {
            "id": "PsovXE",
            "parent": "root",
            "name": "hjkhjkhjk",
            "created_at": 1384354589
          },
          "RbXhdJ": {
            "id": "RbXhdJ",
            "parent": "root",
            "name": "empty",
            "created_at": 1384359806
          }
        },
        "0oVwOM": {
          "HYPSgv": {
            "id": "HYPSgv",
            "parent": "0oVwOM",
            "name": "New node",
            "created_at": 1384342657
          }
        }
      },
      "path": [
        ["Fhs2hL"],
        ["Fhs2hL", "aHxe8k"],
        ["Fhs2hL", "pTlmbh"],
        ["Fhs2hL", "pTlmbh", "0oVwOM"],
        ["Fhs2hL", "pTlmbh", "0oVwOM", "HYPSgv"],
        ["osFIMf"],
        ["PsovXE"],
        ["RbXhdJ"]
      ]
    }
  }; }
  
  var uc = createUC();
  var ucJSON = JSON.stringify(uc);
  
  var isPlainObject = function(obj) {
      return obj != null && typeof obj === 'object'
          && !Array.isArray(obj) && obj.constructor === Object;
  }
  
  var trackable = function(o, stats, prependKey) {
    var out, key;
  
    var stats = stats || [];
    var prependKey = prependKey || '';
  
    if (typeof o !== 'object') {
      return o;
    }
    if (!o) {
      return o;
    }
  
    if ('[object Array]' === Object.prototype.toString.apply(o)) {
      // ignore arrays
      return JSON.parse(JSON.stringify(o));
    }
  
    out = {};
    var each = function(v, k) {
      Object.defineProperty(out, k, {
        enumerable: true,
        get: function() {
          stats.push(catKey);
          return v;
        }
      });
    };
    for (key in o) {
      if (o.hasOwnProperty(key)) {
        var catKey = (prependKey ? prependKey + '.' : '') + key;
        var value = trackable(o[key], stats, catKey);
        each(value, key);
      }
    }
  
    if (!prependKey) {
      Object.defineProperty(out, 'stats', {
        enumerable: true,
        value: function() {
          return stats;
        }
      });
    }
  
    return out;
  };
  
  function recursive(obj) {
    var clone, i;
  
    if (typeof obj !== 'object' || !obj)
      return obj;
  
    if ('[object Array]' === Object.prototype.toString.apply(obj)) {
      clone = [];
      var len = obj.length;
      for (i = 0; i < len; i++)
        clone[i] = recursive(obj[i]);
      return clone;
    }
  
    clone = {};
    for (i in obj)
      if (obj.hasOwnProperty(i))
        clone[i] = recursive(obj[i]);
    return clone;
  }

};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
Deep Copy
var bes = deepCopy(uc)
pending…
JSON Stringify / JSON Parse
var bes = JSON.parse(JSON.stringify(uc))
pending…
clone
var bes = clone(uc);
pending…
recursive
var bes = recursiveDeepCopy(uc);
pending…
$
var bes = jQuery.extend(true, {}, uc);
pending…
lodash
var bes = _.cloneDeep(uc);
pending…
trackable copy
var bes = trackable(uc);
pending…
recurse only - no touch
var bes = recursiveDeepCopyNoTouch(uc);
pending…
JSON parse pre-stringified
var bes = JSON.parse(ucJSON)
pending…
unsafe clone
var bes = unsafeClone(uc)
pending…
initialize new from a function
var bes = createUC();
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL.

0 Comments