Compare nested objects in JavaScript and return keys equality

前端 未结 5 967
伪装坚强ぢ
伪装坚强ぢ 2020-12-31 17:31

I have two nested objects obj1 and obj2 and I want to compare them and the recursively return an object that for each nested key has a equality-lik

相关标签:
5条回答
  • 2020-12-31 17:53

    A recursive example,

    var obj1 = {
            prop1: 1,
            prop2: "foo",
            prop3: {
                prop4: 2,
                prop5: "bar"
            },
            prop7: {},
        }
    
        var obj2 = {
            prop1: 3,
            prop2: "foo",
            prop3: {
                prop4: 2,
                prop5: "foobar"
            },
            prop6: "new",
            prop7: {},
            prop8: {},
        }
    
        var result = {};
    
        function compare(obj1, obj2, obj_) {
            for (let k in obj1) {
                var type = typeof obj1[k];
                if (type === 'object') {
                    obj_[k] = {};
                    if (!obj2[k]){
                        obj_[k] = false;
                    }else if ((Object.entries(obj1[k]).length === 0 && obj1[k].constructor === Object) && (Object.entries(obj2[k]).length === 0 && obj2[k].constructor === Object)) {
                        obj_[k] = true;
                    } else {
                        compare(obj1[k], obj2[k], obj_[k]);
                    }
                } else {
                    obj_[k] = (obj1[k] === obj2[k]);
                }
    
            }
        }
    
        if (Object.keys(obj1).length < Object.keys(obj2).length) { //check if both objects vary in length.
            var tmp = obj1;
            obj1 = obj2;
            obj2 = tmp;
        }
    
        compare(obj1, obj2, result);
    
        console.log(result);

    0 讨论(0)
  • 2020-12-31 17:54

    You could iterate all keys and check the nested objects if both values are objects.

    const isObject = v => v && typeof v === 'object';
    
    function getDifference(a, b) {
        return Object.assign(...Array.from(
            new Set([...Object.keys(a), ...Object.keys(b)]),
            k => ({ [k]: isObject(a[k]) && isObject(b[k])
                ? getDifference(a[k], b[k])
                : a[k] === b[k]
            })
        ));
    }
    
    var obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } },
        obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" }, prop6: "new" };
    
    console.log(getDifference(obj1, obj2));

    0 讨论(0)
  • 2020-12-31 17:54

    Here is a solution that I made recently that can handle the same issue, and it takes an optional key for setting comparison strictness. Useful for when your backend sends a value as a number, but expects that value to be returned as a string. We had been using JSON.stringify comparison, but it is a bit crude, and cant account for when the objects are the same but the keys are in different order.

    public objectsAreTheSame(objA: {[key: string]: any}, objB: {[key: string]: any}, isStrict = false): boolean {
            let areTheSame = true;
    
            const strictLevel = isStrict ? 'isStrict' : 'isNotStrict';
    
            const valuesDoNotMatch = {
                'isStrict': (a, b) => a !== b,
                'isNotStrict': (a, b) => a != b
            };
    
            const isObject = (a, b) => typeof a === 'object' && !Array.isArray(a) && (!!a && !!b);
    
            const compareArrays = (a, b) => {
                if (a.length === b.length) {
                    a.sort();
                    b.sort();
    
                    a.forEach((ele, idx) => compareValues(ele, b[idx]));
                } else {
                    areTheSame = false;
                }
            };
    
            const compareValues = (a, b) => {
                if (Array.isArray(a)) {
                    compareArrays(a, b);
                } else if (!isObject(a, b) && valuesDoNotMatch[strictLevel](a, b)) {
                    areTheSame = false;
                } else if (isObject(a, b) && !this.objectsAreTheSame(a, b, isStrict)) {
                    areTheSame = false;
                }
            };
    
            const keysA = Object.keys(objA);
            const keysB = Object.keys(objB);
    
            if (keysA.length !== keysB.length) return false;
    
            for (let key of keysA) compareValues(objA[key], objB[key]);
    
            return areTheSame;
        }
    
    0 讨论(0)
  • 2020-12-31 18:05

    You could use reduce to build new object and another get method to get nested props from other object by string and compare it to current prop value in first object.

    const obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } }
    const obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" } }
    
    function get(obj, path) {
      return path.split('.').reduce((r, e) => {
        if (!r) return r
        else return r[e] || undefined
      }, obj)
    }
    
    function compare(a, b, prev = "") {
      return Object.keys(a).reduce((r, e) => {
        const path = prev + (prev ? '.' + e : e);
        const value = a[e] === get(b, path);
        r[e] = typeof a[e] === 'object' ? compare(a[e], b, path) : value
        return r;
      }, {})
    }
    
    const result = compare(obj1, obj2);
    console.log(result)

    To compare all properties of both objects you could create extra function that will perform loop by both objects.

    const obj1 = {"prop1":1,"prop2":"foo","prop3":{"prop4":2,"prop5":"bar"},"prop7":{"prop9":{"prop10":"foo"}}}
    const obj2 = {"prop1":3,"prop2":"foo","prop3":{"prop4":2,"prop5":"foobar"},"prop6":"new","prop7":{"foo":"foo","bar":{"baz":"baz"}}}
    
    function get(obj, path) {
      return path.split('.').reduce((r, e) => {
        if (!r) return r;
        else return r[e] || undefined;
      }, obj);
    }
    
    function isEmpty(o) {
      if (typeof o !== 'object') return true;
      else return !Object.keys(o).length;
    }
    
    function build(a, b, o = null, prev = '') {
      return Object.keys(a).reduce(
        (r, e) => {
          const path = prev + (prev ? '.' + e : e);
          const bObj = get(b, path);
          const value = a[e] === bObj;
    
          if (typeof a[e] === 'object') {
            if (isEmpty(a[e]) && isEmpty(bObj)) {
              if (e in r) r[e] = r[e];
              else r[e] = true;
            } else if (!bObj && isEmpty(a[e])) {
              r[e] = value;
            } else {
              r[e] = build(a[e], b, r[e], path);
            }
          } else {
            r[e] = value;
          }
          return r;
        },
        o ? o : {}
      );
    }
    
    function compare(a, b) {
      const o = build(a, b);
      return build(b, a, o);
    }
    
    const result = compare(obj1, obj2);
    console.log(result)

    0 讨论(0)
  • 2020-12-31 18:13

    You could create a merged object which will have keys of both object. Loop through this object and compare the values for both obj1 and obj2 for each key. If the property is an object, recursively compare the properties. This will work for any level of nesting. Since the properties could be missing from either of the objects, default parameter = {} is added.

    const obj1={prop1:1,prop2:"foo",prop3:{prop4:2,prop5:"bar"},prop7:{pro8:"only in 1"}},
          obj2={prop1:3,prop2:"foo",prop3:{prop4:2,prop5:"foobar"}, prop6: "only in 2"};
        
    const isObject = val => typeof val === 'object' && val // required for "null" comparison
    
    function compare(obj1 = {}, obj2 = {}) {
      const output = {},
            merged = { ...obj1, ...obj2 }; // has properties of both
    
      for (const key in merged) {
        const value1 = obj1[key],
              value2 = obj2[key];
    
        if (isObject(value1) || isObject(value2))
          output[key] = compare(value1, value2) // recursively call
        else
          output[key] = value1 === value2
      }
      
      return output;
    }
    
    console.log(compare(obj1, obj2))

    0 讨论(0)
提交回复
热议问题