Is it fine to use JSON.stringify for deep comparisons and cloning?
Asked Answered
H

4

22

After attempting several implementations for deep comparison and copying for JSON-serializable objects, I've noticed the fastest often are just:

function deep_clone(a){
   return JSON.parse(JSON.stringify(a));
};
function is_equal(a,b){
    return JSON.stringify(a) === JSON.stringify(b);
};

I feel like this is cheating, though. Like I'll find some problem that will annoy me on future. Is it fine to use those?

Hypercorrection answered 13/3, 2013 at 2:52 Comment(6)
I only use JSON serializable objects.Hypercorrection
FWIW article being strict about saying 'JSON object' and stating that there is no such thing as 'JSON object' benalman.com/news/2010/03/theres-no-such-thing-as-a-jsonPycnidium
@BenjaminGruenbaum not exactly duplicate, but that guys is using the old JSON lib anyway.Hypercorrection
@Pycnidium so what do I call a JSON-serializable object?Hypercorrection
JSON is now natively implemented in many browsers, which completely changes any performance analysis.Hypercorrection
Fair enough, in that case there is no problem.Ranzini
F
33

JavaScript does not guarantee the order of keys.

If they are entered in the same order, this approach would work most of the time, but it would not be reliable.

Also, it would return false for objects that were deeply equal, but whose keys were entered in a different order:

JSON.stringify({ a: 1, b: 2}) === "{"a":1,"b":2}"

JSON.stringify({ b: 2, a: 1}) === "{"b":2,"a":1}"
Footwork answered 27/9, 2014 at 17:53 Comment(4)
True. That makes the other answer wrong. Reviewed the tick.Hypercorrection
Javascript does guarantee key order, in recent versions. See e.g 2ality.com/2015/10/property-traversal-order-es6.html for an overview. In practice, JS engines were already doing this. I would argue that since the key order is now defined, two objects with the same keys in different order should not be considered equivalent. Thus the JSON.stringify comparison works.Maguire
Has anyone tried to make use of sorting yet? Something along the lines of JSON.stringify({ a: 1, b: 2 }).split("").sort().join("") === JSON.stringify({ b: 2, a: 1 }).split("").sort().join("")Geosynclinal
@Geosynclinal sorting will not work. consider this example JSON.stringify({ a: 12, b: 12 }).split("").sort().join("") === JSON.stringify({ b: 11, a: 22 }).split("").sort().join("") expected false but got true.Leopoldine
C
16

I realize it's an old question, but I just wanted to add a bit more to the answers, since someone might otherwise walk away from this page mistakenly thinking that using JSON.stringify for comparisons/cloning will work without issue so long as it isn't used to compare/clone objects whose members are unordered. (To be fair to the accepted answer, they shouldn't walk away thinking that; it says, "If [the members] are entered in the same order, this approach would work most of the time.")

Code probably illustrates the potential hiccups best:

JSON.stringify(NaN) === JSON.stringify(null)
// => true

JSON.stringify(Infinity) === JSON.stringify(null)
// => true

// or, to put it all together:
JSON.stringify({ val1: (1 / 0), val2: parseInt("hi there"), val3: NaN }) === JSON.stringify({ val1: NaN, val2: null, val3: null })
// => true

// and here's the same example with "cloning" rather than comparison:
JSON.parse(JSON.stringify({ val1: (1 / 0), val2: parseInt("hi there"), val3: NaN }))
// => Object {val1: null, val2: null, val3: null}

These are quirks that can cause trouble even when ordering isn't an issue (which, as others have said, it can be). It's probably not likely in most cases that these quirks will rear their ugly heads, but it's good to be aware of them, since they could result in some really hard to find bugs.

Cornwallis answered 15/3, 2016 at 13:3 Comment(3)
Thanks for the addition!Hypercorrection
The question asks about "deep comparison and copying for JSON-serializable objects", so Infinity, NaN and other values that aren't part of JSON aren't really any more relevant than objects that contain functions, window objects, undefined, regexes, etc...Maguire
As someone who got here looking for answers for the more general "not necessarily JSON-serializable objects", seeing as this is not mentioned in the title, I really appreciate the additions provided in this answer. I think people often forget that answers here on SO also serve as reference for other people facing similiar issues, not to mention having such negative attitude towards people giving additional information is actually detrimental to the community.Kiona
B
0

As long as the key-value pairs are always in the same order, yes, you can use stringify to compare using the deep equals operator (===).

Behah answered 13/3, 2013 at 2:59 Comment(1)
it's strict equality, not deep equality operator and you can use regular equality (==) aswell to compare two strings from JSON.stringifyErrhine
B
0

I wrote this function to deep compare any object array or value: Use it if you want :) I tested it with very huge sample of objects with randomized entry order in objects and arrays too.

function c(x, y) {
  if (!x && !y) return !0
  if (!x || !y) return !1
  if (typeof (x) !==
      typeof (y)) return !1
  if (x instanceof Array) {
    if (
      x.length != y.length) return !1
    const f = []
    for (let i = 0; i < x.length; i++) {
      if (!c(x[i], y[i])) f.push(i)
    }
    const g = [...f]
    for (const i of f) {
      let r = !1
      for (const j of g) {
        if (
          c(x[i], y[j])) {
          g.splice(g.indexOf(j), 1)
          r++
          break
        }
      }
      if (!r) { return !1 }
    }
    return !0
  } else if (x instanceof Object) {
    const e1 =
          Object.entries(x)
    try {
      return c(e1, r(Object.entries(y),
        e1))
    } catch (e) {
      return !1
    }
  } else {
    return x === y
  }

  function r(
    u, v) {
    const a = []
    if (u.length != v.length) return u
    for (
      let i = 0; i < v.length; i++) {
      a.push(m(u, v[i][0]))
    }
    return a
  }

  function m(a, k) {
    for (let i = 0; i < a.length; i++) {
      if (a[i][0] === k) return [a[i][0], a[i][1]]
    }
    throw 0
  }
}
Brainard answered 4/2, 2022 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.