Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3409256
D7753.id27258.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
D7753.id27258.diff
View Options
diff --git a/lib/utils/objects.js b/lib/utils/objects.js
--- a/lib/utils/objects.js
+++ b/lib/utils/objects.js
@@ -4,9 +4,11 @@
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<K, T> = { +[key: K]: T };
+type ObjectMap<K, T> = { +[key: K]: T };
+type NestedObjectMap<K, T> = { +[key: K]: T | NestedObjectMap<K, T> };
function findMaximumDepth(obj: Object): ?{ path: string, depth: number } {
let longestPath = null;
@@ -37,7 +39,7 @@
return { path: longestPath, depth: longestDepth };
}
-function values<K, T>(map: Map<K, T>): T[] {
+function values<K, T>(map: ObjectMap<K, T>): T[] {
return Object.values
? // https://github.com/facebook/flow/issues/2221
// $FlowFixMe - Object.values currently does not have good flow support
@@ -45,6 +47,21 @@
: Object.keys(map).map((key: K): T => map[key]);
}
+function keys<K, T>(map: ObjectMap<K, T>): K[] {
+ return Object.keys(map);
+}
+
+function assignValueWithKey<K, T>(
+ obj: NestedObjectMap<K, T>,
+ key: K,
+ value: T | NestedObjectMap<K, T>,
+): NestedObjectMap<K, T> {
+ return {
+ ...obj,
+ ...Object.fromEntries([[key, value]]),
+ };
+}
+
function hash(obj: ?Object): number {
if (!obj) {
return -1;
@@ -52,9 +69,36 @@
return stringHash(stableStringify(obj));
}
+// returns an object with properties from obj1 not included in obj2
+function deepDiff<K, T>(
+ obj1: NestedObjectMap<K, T>,
+ obj2: NestedObjectMap<K, T>,
+): NestedObjectMap<K, T> {
+ let diff: NestedObjectMap<K, T> = {};
+ keys(obj1).forEach((key: K) => {
+ if (_isEqual(obj1[key], obj2[key])) {
+ return;
+ }
+
+ if (!_isPlainObject(obj1[key]) || !_isPlainObject(obj2[key])) {
+ diff = assignValueWithKey(diff, key, obj1[key]);
+ return;
+ }
+
+ const nestedObj1: ObjectMap<K, T> = (obj1[key]: any);
+ const nestedObj2: ObjectMap<K, T> = (obj2[key]: any);
+
+ const nestedDiff = deepDiff(nestedObj1, nestedObj2);
+ if (Object.keys(nestedDiff).length > 0) {
+ diff = assignValueWithKey(diff, key, nestedDiff);
+ }
+ });
+ return diff;
+}
+
function assertObjectsAreEqual<K, T>(
- processedObject: Map<K, T>,
- expectedObject: Map<K, T>,
+ processedObject: ObjectMap<K, T>,
+ expectedObject: ObjectMap<K, T>,
message: string,
) {
const processedObjectKeys = Object.keys(processedObject);
@@ -75,4 +119,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],
+ });
+ });
+});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Dec 5, 3:24 PM (1 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2616408
Default Alt Text
D7753.id27258.diff (4 KB)
Attached To
Mode
D7753: [lib] introduce function to find deep difference between two objects
Attached
Detach File
Event Timeline
Log In to Comment