Last active
November 30, 2018 04:01
-
-
Save jasonrhodes/8c56febf0f8a87cea8ee0a6c5865faf1 to your computer and use it in GitHub Desktop.
Compares two objects and gives you detailed diff results about added, removed, changed, and ref-only changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* flattens an object into a single dimension with dot-separated key paths | |
* | |
* e.g. | |
* const test = { a: 'hello', b: { xx: 'cool', yy: 'yeah' }, c: [1, 2] } | |
* flatten(test) => { 'a': 'hello', 'b.xx': 'cool', 'b.yy': 'yeah', 'c.0': 1, 'c.1': 2 } | |
*/ | |
function flatten(o, path = []) { | |
return Object.entries(o).reduce((result, [key, value]) => { | |
const localPath = [...path, key]; | |
if (typeof value === 'object' && value !== null) { | |
return { ...result, ...flatten(value, localPath) }; | |
} | |
return { ...result, [localPath.join('.')]: value }; | |
}, {}); | |
} | |
/** | |
* Return object that includes all keys from b exclusive of a | |
* | |
* e.g. | |
* const joe = { name: 'joe', age: 21 } | |
* const jane = { name: 'jane', birthplace: 'london' } | |
* filterExclusiveKeys(joe, jane) => { birthplace: 'london' } | |
* filterExclusiveKeys(jane, joe) => { age: 21 } | |
*/ | |
function filterExclusiveKeys(a, b) { | |
const exclusiveKeys = Object.keys(b).filter(key => !Object.keys(a).includes(key)); | |
return exclusiveKeys.reduce((result, key) => ({ ...result, [key]: b[key] }), {}); | |
} | |
/** | |
* Takes a flattened hash and gives you a list of all the root keys | |
* | |
* e.g. | |
* const colors = { 'a.b.c': 'red', 'x.y.z': 'blue' } | |
* findRootKeys(colors) => ['a', 'x'] | |
*/ | |
function findRootKeys(flatHash) { | |
const rootKeys = Object.keys(flatHash).map((k) => k.split('.')[0]); | |
return [ ...new Set(rootKeys) ]; // unique | |
} | |
function diff(p, n) { | |
const prev = flatten(p); | |
const next = flatten(n); | |
const added = filterExclusiveKeys(prev, next); | |
const removed = filterExclusiveKeys(next, prev); | |
const changedKeys = Object.keys(next).filter(key => { | |
if (Object.keys(added).includes(key)) { | |
return false; | |
} | |
if (Object.keys(removed).includes(key)) { | |
return false; | |
} | |
if (prev[key] === next[key]) { | |
return false; | |
} | |
return true; | |
}); | |
const changed = changedKeys.reduce((result, key) => { | |
return { ...result, [key]: `${prev[key]} => ${next[key]}` }; | |
}, {}); | |
const changedRoots = [ | |
...findRootKeys(added), | |
...findRootKeys(removed), | |
...findRootKeys(changed) | |
]; | |
const refs = Object.keys(n).filter( | |
key => | |
!changedRoots.includes(key) && | |
typeof n[key] === 'object' && | |
n[key] !== p[key] | |
); | |
return { added, removed, changed, refs }; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const prevProps = { | |
name: { | |
first: "Jo", | |
last: "Rogers" | |
}, | |
age: 39, | |
address: { | |
street: "Gum Drop Ln", | |
number: 123, | |
zip: 22222 | |
} | |
} | |
const nextProps = { | |
name: { | |
first: "Jo", | |
last: "Rogers" | |
}, | |
age: 40, | |
address: { | |
city: "Albuquerque", | |
zip: 22223 | |
} | |
} | |
console.log( | |
JSON.stringify( | |
diff(prevProps, nextProps), | |
null, | |
2 | |
) | |
); | |
// produces: | |
{ | |
"added": { | |
"address.city": "Albuquerque" | |
}, | |
"removed": { | |
"address.street": "Gum Drop Ln", | |
"address.number": 123 | |
}, | |
"changed": { | |
"age": "39 => 40", | |
"address.zip": "22222 => 22223" | |
}, | |
"refs": [ | |
"name" | |
] | |
} | |
// NOTE: name shows up in refs here because the deep values all stayed the same | |
// but the object _reference_ changed - this only works for top-level keys | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment