Skip to content

Instantly share code, notes, and snippets.

@petsel
Created February 19, 2024 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petsel/2282bc694959af5f128c07aaba18c397 to your computer and use it in GitHub Desktop.
Save petsel/2282bc694959af5f128c07aaba18c397 to your computer and use it in GitHub Desktop.
/**
* - 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