Created
February 19, 2024 16:38
-
-
Save petsel/2282bc694959af5f128c07aaba18c397 to your computer and use it in GitHub Desktop.
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
/** | |
* - include core-js for enabling/implementing the new `Set` methods | |
* | |
* <script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.36.0/minified.js"></script> | |
* | |
* - for related tasks, like splitting any given keypath most generically into key-partials, | |
* have a look at ... [https://regex101.com/r/tew8gr/3] ... and its regular expression ... | |
* | |
* possible usage: | |
* | |
* [ | |
* ...`bar.foo["bar.baz\\".\\"booz"][9].booz.'bar.baz\\'.\\'booz'.baz[0].foo."34".foo.45.bar` | |
* .matchAll(/(?<arrayEntry>\[\d+\])|(?:(?<!\\)"(?<doubleQuoted>.+?(?<!\\))")|(?:(?<!\\)'(?<singleQuoted>.+?(?<!\\))')|\b(?<regularKey>[\p{L}\p{N}]+)\b/gu) | |
* ] | |
* .map(({ groups: { arrayEntry, doubleQuoted, singleQuoted, regularKey } }) => (arrayEntry || doubleQuoted || singleQuoted || regularKey)) | |
*/ | |
function concatKeypath(keypath, key) { | |
// - test for a key which can be used with dot chaining. | |
// - see ... [https://regex101.com/r/tew8gr/1] | |
return (/^[_$\p{L}][_$\p{L}\p{N}]*$/u).test(key) | |
// - concat a dot chained key. | |
? (!!keypath && `${ keypath }.${ key }` || key) | |
// - concat a quoted key with square bracket notation. | |
: `${ keypath }["${ key }"]`; | |
} | |
function mapDataStructure(value, keypath = '', map = new Map) { | |
if (Array.isArray(value)) { | |
value | |
.forEach((item, idx) => | |
mapDataStructure(item, `${ keypath }[${ idx }]`, map) | |
); | |
} else if (value && typeof value === 'object') { | |
Object | |
.entries(value) | |
.forEach(([key, val]) => | |
mapDataStructure(val, concatKeypath(keypath, key), map) | |
); | |
} else { | |
map.set(keypath, value); | |
} | |
return map; | |
} | |
function diffDataValuesByKeypath(recentData, currentData) { | |
const recentMap = mapDataStructure(recentData); | |
const currentMap = mapDataStructure(currentData); | |
const recentPaths = new Set([...recentMap.keys()]); | |
const currentPaths = new Set([...currentMap.keys()]); | |
const collidingPaths = recentPaths.intersection(currentPaths); | |
const uniqueRecentPaths = recentPaths.difference(currentPaths); | |
const uniqueCurrentPaths = currentPaths.difference(recentPaths); | |
return { | |
changed: [...collidingPaths.values()] | |
.reduce((result, keypath) => { | |
const recentValue = recentMap.get(keypath); | |
const currentValue = currentMap.get(keypath); | |
if (!Object.is(recentValue, currentValue)) { | |
result[keypath] = { | |
recent: recentValue, | |
current: currentValue, | |
}; | |
} | |
return result; | |
}, {}), | |
deleted: [...uniqueRecentPaths.values()] | |
.reduce((result, keypath) => { | |
result[keypath] = recentMap.get(keypath); | |
return result; | |
}, {}), | |
added: [...uniqueCurrentPaths.values()] | |
.reduce((result, keypath) => { | |
result[keypath] = currentMap.get(keypath); | |
return result; | |
}, {}), | |
}; | |
} | |
// - also have a look. of a running version of the next provided test code. | |
// see ... [https://stackoverflow.com/a/77995849/2627243] ... | |
// as of ... [https://stackoverflow.com/questions/77994125/how-to-identify-changed-property-values-when-comparing-two-nested-data-structure] | |
const oldObject = { | |
name: 'John', age: 30, | |
friends: ['ali', 'qasim', { foo: 'Foo', 'foo bar': { baz: 'Baz', biz: 'Biz' } }], | |
}; | |
const newObject = { | |
name: 'John', age: 35, city: 'New York', | |
friends: ['ali', 'haider', { 'foo bar': { baz: 'BAZ'}, foo: 'Foo' }], | |
}; | |
const diff = diffDataValuesByKeypath(oldObject, newObject); | |
console.log({ oldObject, newObject, diff }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment