0

Let's say I have two objects, which might have some properties in common. The overall idea is that one is used as a schema to compare it to the other. I would like to compare values of common properties and construct a new object representing these comparisons of each individual property. In the result, any properties in common should have the value of comparing the source properties for equality (e.g. result.a.b.c = (obj1.a.b.c == obj2.a.b.c)). Any properties that exist on only one of the two objects would be copied to the result with their original values.

For example, consider the following objects to compare:

const schema = {
        num: 123,
        str: 'hello',
        nested: {
            num: 123,
            str: 'hello',
        },
    },

    doc = {
        nums: [1,2,3],
        str: 'hello',
        nested: {
            num: 123,
            str: 'world',
            foo: 'bar',
        },
    };

The comparison result should be:

{
    nums: [1,2,3], // obj2.nums
    num: 123,  // obj1.num
    str: true, // obj1.str == obj2.str
    nested: {
        num: true,  // obj1.nested.num == obj2.nested.num
        str: false, // obj1.nested.str == obj2.nested.str
        foo: 'bar', // obj2.nested.foo
    },
}

How would I perform this comparison in the most efficient time and space way?

Currently I have this implementation:

function deepCompare(obj1, obj2) {
    const validatedObject = {}

    Object.keys(obj1).forEach(e => {
        if(object[e].constructor.name === 'Object') {
            validatedObject[e] = deepCompare(obj1[e], obj2[e])
        } else {
            validatedObject[e] = obj1[e] === obj2[e]
        }
    })

    return validatedObject
}

Are there any ways to do this more efficiently without using JSON.stringify, because I might have property that is missing, but I still want to return the fact that the values are correct despite the schema of those objects?

Maybe for in or for of loop? But how would I test their effectiveness? It's okay for small objects, but sometimes they get really big and I want to compare them in the most efficient way possible.

NOTE for editors.

I understand that this might seem like a duplicate, but it in fact is not, here I'm looking for a most efficient way to compare values, iteratively, recursively or some other way, not objects themselves

outis
  • 75,655
  • 22
  • 151
  • 221
ColdHands
  • 947
  • 8
  • 28
  • So you want `true` when all common properties have the same value and `false` otherwise, correct? – Arleigh Hix Jun 26 '22 at 20:28
  • Correct, I want to find the fastest way to do that and I'm not sure if my algo is the most efficient as it could be. – ColdHands Jun 26 '22 at 20:32
  • 1
    Can you be more precise about "*they might miss a property*", please? Does that mean the second object doesn't need to have all the same properties as the first object? Or the other way round (so that it might have more properties)? If all properties are optional, wouldn't you just always return true? – Bergi Jun 26 '22 at 20:51
  • 1
    `object[e].constructor.name === 'Object'` is a questionable check. It won’t work for arrays, nulls, undefineds, `Date`s…. If you never have any of those, `typeof object[e] === 'object'` is cleaner. – Ry- Jun 26 '22 at 20:58
  • @Bergi some object might miss a property, either 1st or second, here i'm compairing the values, so the logic is _if obj1 has a key and obj2 has the same key, we compare the values of those keys_. And if the `values` differ we return false, rather than we compare the objects themselves. – ColdHands Jun 26 '22 at 21:01
  • An example would have been nice. Like `{}` == `{x:2}` whereas `{x:2}` != `{x:3}` – IT goldman Jun 26 '22 at 21:09
  • @ITgoldman better would be `{x:2, y:3} == {x:2, z:1}` but `{x:1, y:3} != {x:2, z:1}`. But @ColdHands the statement *'...we do not compare the objects themselves, we compare the values'* is not accurate, you are comparing the intersection of properties (keys and values). To compare only values then `{foo: 1}` would equal `{bar: 1}` – pilchard Jun 26 '22 at 21:15
  • 1
    Does this answer your question? [Compare properties that exist in both objects](https://stackoverflow.com/questions/34085102/compare-properties-that-exist-in-both-objects) – pilchard Jun 26 '22 at 21:16
  • Seems closer to "[Compare nested objects in JavaScript and return keys equality](//stackoverflow.com/q/55591096/90527)". – outis Jun 26 '22 at 22:30
  • @ColdHands: do you really want to copy properties not in common to the result, as per "if there are some extra keys, we just leave them with their values"? Or do you want to leave them off of the result (which would make more sense for validation)? – outis Jun 26 '22 at 22:34
  • Why is the constructor name used to check for objects, rather than the type (`'object' == typeof(object[e])`? Put another way, should only literal objects be deep-compared, or any value with type `object`? How should array values be compared? – outis Jun 26 '22 at 22:39

3 Answers3

2

the logic is if obj1 has a key and obj2 has the same key, we compare the values of those keys

Then you should use

function compare(a, b) {
    if (a === b) return true;
    if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) {
        return Object.keys(a).every(k =>
            !(k in b) || compare(a[k], b[k])
        );
    }
    return false;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I think that `Object.keys(a)` is slower than `for (var k in a)` because it get's first all the keys before comparing (could be 1e9 keys). – IT goldman Jun 26 '22 at 21:25
  • @ITgoldman Modern engines can optimise this pattern just fine. Sure, do a benchmark, but I wouldn't expect a `for in` loop to be considerably faster. – Bergi Jun 26 '22 at 21:28
1

I think your solution is good enough, it's only missing some default checks (you don't need to iterate through the objects keys if you have the same refference for example).

Also, there's this package that claims is the fastest deep equal checker.

enter image description here

You can look over the code to see if you missed any other condition (it's still a recursive solution for the objects, but there are some other data types treated there if you're interested).

There is also this article if you prefer written material instead of code that has more checks before recursive calls

  • 1
    This is actually a nice solution, even though it has more checks, this performs really really fast, thank you. – ColdHands Jun 26 '22 at 21:20
0

What you want to do is first find the common object keys. Then as you loop through the properties you want to return false as soon as you find a mismatch. In the snippet below I'm using an every loop which will stop as soon as one iteration returns false. If you return a value in the if block you do not need the else block.

function deepCompare(obj1, obj2) {
  const commonKeys = Object.keys(obj1).filter(x => Object.keys(obj2).includes(x));
  let valid = true;
  commonKeys.every(e => {
    if (obj1[e].constructor.name === 'Object') {
      console.log('object', e, obj1[e], obj2[e])
      valid = deepCompare(obj1[e], obj2[e])
      return valid // true goes to next, false ends the .every loop
    }
    console.log('not object', e, obj1[e] === obj2[e] ? 'match' : 'mismatch')
    valid = obj1[e] === obj2[e]
    return valid // true goes to next, false ends the .every loop
  })

  return valid // returns the first false or true if everything passes
}

const obj1 = {
  num: 123,
  str: 'hello',
  nested: {
    num: 123,
    str: 'hello',
  }
}
const obj3 = {
  num: 1233,
  str: 'hello'
}
const obj4 = {
  num: 123,
  str: 'hello',
  nested: {
    num: 123,
    str: 'hellure',
  }
}

console.log(deepCompare(obj1, obj1.nested))
console.log(deepCompare(obj1, obj3))
console.log(deepCompare(obj1, obj4))
Arleigh Hix
  • 9,990
  • 1
  • 14
  • 31