diff --git a/lib/utils/objects.js b/lib/utils/objects.js --- a/lib/utils/objects.js +++ b/lib/utils/objects.js @@ -4,6 +4,7 @@ import invariant from 'invariant'; import _difference from 'lodash/fp/difference.js'; import _isEqual from 'lodash/fp/isEqual.js'; +import _isPlainObject from 'lodash/fp/isPlainObject.js'; import stringHash from 'string-hash'; type Map = { +[key: K]: T }; @@ -52,6 +53,27 @@ return stringHash(stableStringify(obj)); } +// returns an object with properties from obj1 not included in obj2 +function deepDiff(obj1: Object, obj2: Object): Object { + const diff = {}; + for (const key in obj1) { + if (key in obj2 && _isEqual(obj1[key], obj2[key])) { + continue; + } + + if (!_isPlainObject(obj1[key]) || !_isPlainObject(obj2[key])) { + diff[key] = obj1[key]; + continue; + } + + const nestedDiff = deepDiff(obj1[key], obj2[key]); + if (Object.keys(nestedDiff).length > 0) { + diff[key] = nestedDiff; + } + } + return diff; +} + function assertObjectsAreEqual( processedObject: Map, expectedObject: Map, @@ -75,4 +97,4 @@ ); } -export { findMaximumDepth, values, hash, assertObjectsAreEqual }; +export { findMaximumDepth, values, hash, assertObjectsAreEqual, deepDiff }; diff --git a/lib/utils/objects.test.js b/lib/utils/objects.test.js new file mode 100644 --- /dev/null +++ b/lib/utils/objects.test.js @@ -0,0 +1,103 @@ +// @flow + +import { deepDiff } from './objects.js'; + +describe('deepDiff tests', () => { + it('should return an empty object if the objects are identical', () => { + const obj1 = { + key1: 'value1', + key2: { foo: 'bar' }, + }; + const obj2 = { + key1: 'value1', + key2: { foo: 'bar' }, + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({}); + }); + + it('should return the differences between two objects', () => { + const obj1 = { + key1: 'value1', + key2: { prop: 'a' }, + }; + const obj2 = { + key1: 'value2', + key2: { prop: 'b' }, + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({ + key1: 'value1', + key2: { + prop: 'a', + }, + }); + }); + + it('should handle objects nested in objects', () => { + const obj1 = { + key1: 'value1', + key2: { prop: 'a', nested: { xyz: 123 } }, + }; + const obj2 = { + key1: 'value1', + key2: { prop: 'a', nested: { xyz: 124 } }, + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({ + key2: { + nested: { + xyz: 123, + }, + }, + }); + }); + + it('should handle nested objects with null and undefined values', () => { + const obj1 = { + key1: null, + key2: { prop: undefined }, + }; + const obj2 = { + key1: undefined, + key2: { prop: null }, + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({ + key1: null, + key2: { + prop: undefined, + }, + }); + }); + + it('should handle objects with different value types', () => { + const obj1 = { + key1: 'value1', + key2: 123, + }; + const obj2 = { + key1: 'value1', + key2: '123', + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({ + key2: 123, + }); + }); + + it('should handle objects with array value types', () => { + const obj1 = { + key1: ['value1'], + key2: ['a', 1], + }; + const obj2 = { + key1: ['value1'], + key2: ['a', 2], + }; + const diff = deepDiff(obj1, obj2); + expect(diff).toEqual({ + key2: ['a', 1], + }); + }); +});