-
-
Save Yimiprod/7ee176597fef230d1451 to your computer and use it in GitHub Desktop.
/** | |
* This code is licensed under the terms of the MIT license | |
* | |
* Deep diff between two object, using lodash | |
* @param {Object} object Object compared | |
* @param {Object} base Object to compare with | |
* @return {Object} Return a new object who represent the diff | |
*/ | |
function difference(object, base) { | |
function changes(object, base) { | |
return _.transform(object, function(result, value, key) { | |
if (!_.isEqual(value, base[key])) { | |
result[key] = (_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value; | |
} | |
}); | |
} | |
return changes(object, base); | |
} |
Maybe not the fastest, and does not cover many features added above but quite concise and readable alternative:
const objectDiff = (a, b) => _.fromPairs(_.differenceWith(_.toPairs(a), _.toPairs(b), _.isEqual))
worked !! thanks 👍
That's fantastic! Thanks!
Thanks a lot to everyone to let that gist be alive with a lot of propositions, don't know if i have the time to update it to a newer / better version, there's a lot of good proposals !
Awesome. Thanks. Perfect solution to what I am looking for.
Awesome man ! cheers from Colorado
Having 2 arrays that are equal at the start(a,b) and then manipulating only array b, doesn't always show that there's a diff.
For example if from the starting, while they are equal, I add an item to b, it shows a difference,
but if I remove an item from b, it doesn't show that there was a difference.
One way to solve it is -
const diff = [...difference(a,b), ...difference(b,a)]
That way I get the difference, whether items were added or removed from b.
Thanks!!!
Mi contribution:
function difference(object, base) {
return _.transform(object, (result, value, key) => {
if (!_.isEqual(value, base[key])) {
result[key] = (_.isObject(value) && _.isObject(base[key])) ? difference(value, base[key]) : value;
}
});
}
@srijan-devops I hope this function helps you:
function clean (obj) {
// Check nil values
if (_.isNil(obj)) return null;
// Check array values
if (_.isArray(obj)) {
const newValue = _.filter(_.map(obj, clean), _.identify);
return _.isEmpty(newValue) ? null : newValue;
}
// Check object values
if (_.isObject(obj)) {
const newValue = _.transform(obj, (result, value, key) => {
const newValue = clean(value);
if (!_.isNil(newValue)) {
result[key] = newValue;
}
}, {});
return _.isEmpty(newValue) ? null : newValue;
}
// Other values
return obj;
}
This is my vanilla way, i hope it help somebody.
/**
* Recursive diff between two object
* @param {Object} base Object to compare with
* @param {Object} object Object compared
* @return {Object} Return a new object who represent the diff OR Return FALSE if there is no difference
*/
function differenceBetweenObjects (base,object){
let diff = false;
if(typeof object == 'object'){
for(let k in object){
if(typeof object[k] != 'object'){
if(base[k] != object[k]){
if(diff == false){diff = {};}
diff[k] = object[k];
}
}
else{
let subDiff = differenceBetweenObjects(base[k],object[k]);
if(subDiff !== false){
if(diff == false){diff = {};}
diff[k] = subDiff;
}
}
}
}
if(typeof base == 'object'){
for(let k in base){
if(!object.hasOwnProperty(k)){
diff[k] = null;
}
}
}
return diff;
}
This is my vanilla way, i hope it help somebody.
/** * Recursive diff between two object * @param {Object} base Object to compare with * @param {Object} object Object compared * @return {Object} Return a new object who represent the diff OR Return FALSE if there is no difference */ function differenceBetweenObjects (base,object){ let diff = false; if(typeof object == 'object'){ for(let k in object){ if(typeof object[k] != 'object'){ if(base[k] != object[k]){ if(diff == false){diff = {};} diff[k] = object[k]; } } else{ let subDiff = differenceBetweenObjects(base[k],object[k]); if(subDiff !== false){ if(diff == false){diff = {};} diff[k] = subDiff; } } } } return diff; }
Doesn't work with this:
differenceBetweenObjects({a: 2, b: [1,2,3,4], c: {r: 34534}}, {a: 1, b: [1,2,3,4,5]})
This is my vanilla way, i hope it help somebody.
/** * Recursive diff between two object * @param {Object} base Object to compare with * @param {Object} object Object compared * @return {Object} Return a new object who represent the diff OR Return FALSE if there is no difference */ function differenceBetweenObjects (base,object){ let diff = false; if(typeof object == 'object'){ for(let k in object){ if(typeof object[k] != 'object'){ if(base[k] != object[k]){ if(diff == false){diff = {};} diff[k] = object[k]; } } else{ let subDiff = differenceBetweenObjects(base[k],object[k]); if(subDiff !== false){ if(diff == false){diff = {};} diff[k] = subDiff; } } } } return diff; }
Doesn't work with this:
differenceBetweenObjects({a: 2, b: [1,2,3,4], c: {r: 34534}}, {a: 1, b: [1,2,3,4,5]})
I forgot detecting missing keys from base object. Try now. Thanks the report.
/**
* Deep diff between two object, using lodash
* @param {Object} base Object to compare with
* @param {Object} object Object compared
* @return {Object} Return a new object who represent the diff OR Return FALSE if there is no difference
*/
function differenceBetweenObjects (base,object){
let diff = false;
if(typeof object == 'object'){
for(let k in object){
if(typeof object[k] != 'object'){
if(base[k] != object[k]){
if(diff == false){diff = {};}
diff[k] = object[k];
}
}
else{
let subDiff = differenceBetweenObjects(base[k],object[k]);
if(subDiff !== false){
if(diff == false){diff = {};}
diff[k] = subDiff;
}
}
}
}
if(typeof base == 'object'){
for(let k in base){
if(!object.hasOwnProperty(k)){
diff[k] = null;
}
}
}
return diff;
}
I am thinking. Should I check missing keys recusive if base object's item is an object? Like this:
function differenceBetweenObjects (base,object){
let diff = false;
if(typeof object == 'object' && typeof base == 'object'){
for(let k in object){
if(typeof object[k] != 'object'){
if(base[k] != object[k]){
if(diff == false){diff = {};}
diff[k] = object[k];
}
}
else{
let subDiff = differenceBetweenObjects(base[k],object[k]);
if(subDiff !== false){
if(diff == false){diff = {};}
diff[k] = subDiff;
}
}
}
}
if(typeof base == 'object'){
for(let k in base){
if(typeof object != 'object' || !object.hasOwnProperty(k)){
if(diff == false){diff = {};}
if(typeof base[k] == 'object'){
let subDiff = differenceBetweenObjects(base[k]);
diff[k] = subDiff;
}
else{
diff[k] = null;
}
}
}
}
return diff;
}
If you also want to know what the differences are:
https://stackoverflow.com/questions/8572826/generic-deep-diff-between-two-objects
Thank you, very nice and clean!
I agree with you @jvanderberg and I used it for typescipt!
thanks , it helps a lot
Awesome code! I did a small change to deal with an array of objects as a key of the object we wanted to diff:
export const deepDiffBetweenObjects = (object, base) => {
const changes = (object, base) => {
return transform(object, (result, value, key) => {
if (!isEqual(value, base[key])) {
if (isArray(value)) {
result[key] = difference(value, base[key])
} else if (isObject(value) && isObject(base[key])) {
result[key] = changes(value, base[key])
} else {
result[key] = value
}
}
})
}
return changes(object, base)
}
@chtseac Yours was the best solution I've found so far, thank you from Dublin!
Sharing my own update - I needed to only compare some top-level properties of two objects, so added an optional third parameter to take an array of paths to specifically check.
let changes = deepDiff(previousValue, newValue, [
"customisations",
"customisations.localeOverrides", // won't work currently - someone smarter can me can try make it work :)
"onboarding"
]);
function deepDiff(fromObject, toObject, specificPaths) {
const changes = {};
console.log('specificPaths:', specificPaths);
const buildPath = (path, obj, key) =>
_.isUndefined(path) ? key : `${path}.${key}`;
let obj1 = {}; obj2 = {}
if(specificPaths && specificPaths.length > 0){
// only look at specific paths if specified
for (const path of specificPaths) {
if(fromObject[path]) obj1[path] = fromObject[path];
if(toObject[path]) obj2[path] = toObject[path]
}
} else {
obj1 = fromObject
obj2 = toObject
}
const walk = (fromObject, toObject, path) => {
for (const key of _.keys(fromObject)) {
const currentPath = buildPath(path, fromObject, key);
if (!_.has(toObject, key)) {
changes[currentPath] = { from: _.get(fromObject, key) };
}
}
for (const [key, to] of _.entries(toObject)) {
const currentPath = buildPath(path, toObject, key);
if (!_.has(fromObject, key)) {
changes[currentPath] = { to };
} else {
const from = _.get(fromObject, key);
if (!_.isEqual(from, to)) {
if (_.isObjectLike(to) && _.isObjectLike(from)) {
walk(from, to, currentPath);
} else {
changes[currentPath] = { from, to };
}
}
}
}
};
walk(obj1, obj2);
return changes;
}
Here is an update of @Chippd with specifics paths
`
function deepDiff(fromObject, toObject, specificPaths) {
const changes = {};
console.log('specificPaths:', specificPaths);
const buildPath = (path, __, key) =>
_.isUndefined(path) ? key : `${path}.${key}`;
let obj1 = {};
let obj2 = {}
if (_.isArray(specificPaths) && !_.isEmpty(specificPaths)) {
for (const path of specificPaths) {
if (_.has(fromObject, path)) {
_.set(obj1, path, _.get(fromObject, path));
} else if (_.has(toObject, path)) {
changes[path] = {to: _.get(toObject, path)};
}
if (_.has(toObject, path)) {
_.set(obj2, path, _.get(toObject, path));
} else if (_.has(fromObject, path)) {
changes[path] = {from: _.get(fromObject, path)};
}
}
} else {
obj1 = fromObject
obj2 = toObject
}
const walk = (fromObject, toObject, path) => {
for (const key of _.keys(fromObject)) {
const currentPath = buildPath(path, fromObject, key);
if (!_.has(toObject, key)) {
changes[currentPath] = { from: _.get(fromObject, key) };
}
}
for (const [key, to] of _.entries(toObject)) {
const currentPath = buildPath(path, toObject, key);
if (!_.has(fromObject, key)) {
changes[currentPath] = { to };
} else {
const from = _.get(fromObject, key);
if (!_.isEqual(from, to)) {
if (_.isObjectLike(to) && _.isObjectLike(from)) {
walk(from, to, currentPath);
} else {
changes[currentPath] = { from, to };
}
}
}
}
};
walk(obj1, obj2);
return changes;
}
const previousValue = {
customisations: {
localeOverrides: {
foo: 1,
},
bar: 2
},
bar: [1,2,3],
onboarding: {
foo: 1
},
foo: 1
}
const newValue = {
customisations: {
localeOverrides: {
daz: 1,
},
bar: 2,
daz: 2
},
onboarding: {
foo: 4
},
baz: 2
}
const changes = deepDiff(previousValue, newValue, [
"customisations",
"customisations.localeOverrides",
"onboarding"
]);
// Only specific path
const changes2 = deepDiff(previousValue, newValue, [
"customisations.localeOverrides",
"bar"
]);
// test only if validate that array is present and isn't empty
const changes3 = deepDiff(previousValue, newValue, []);
// no array present
const changes4 = deepDiff(previousValue, newValue);
console.log('compare result various paths', changes);
console.log('compare result Only specific path', changes2);
console.log('compare result test only if validate that array is present and isn't empty', changes3);
console.log('compare result no array present', changes4);
`
Just one example works fine in my case (shallow diff):
const diffData = _.fromPairs( _.differenceWith(_.toPairs(sourceData), _.toPairs(valuesToDiffWith), _.isEqual), )
any suggestions for deep diff?
thank you, it helps me and makes me look prop :). lodash is superbe
Just another shallow diff
const shallowDiff = Object.entries(object1).reduce(
(diff, [key, value]) =>
_isEqual(object2[key], value) ? diff : { ...diff, [key]: value },
{},
)
@Andrei-Fogoros didn't thinked about licence at the time i posted it, but since you're asking, it's a good occasion to put it under MIT License.
@Andrei-Fogoros didn't thinked about licence at the time i posted it, but since you're asking, it's a good occasion to put it under MIT License.
Great, thank you very much! :)
Not sure if anyone needs this variation, but here's one I made to basically create a separate JSON object with ONLY the changes with accurate child items.
import * as _ from 'lodash';
/**
* Deep diff between two object-likes
* @param {Object} fromObject the original object
* @param {Object} toObject the updated object
* @return {Object} a new object which represents the diff
*/
export function deepDiff(fromObject, toObject) {
const changes = {};
const buildPath = (path, obj, key) => {
const origVal = _.get(obj, key);
if (_.isUndefined(path)) {
if (_.isArray(origVal)) {
changes[key] = [];
} else if (_.isObject(origVal)) {
changes[key] = {};
}
} else {
if (_.isArray(origVal)) {
path[key] = [];
} else if (_.isObject(origVal)) {
path[key] = {};
}
}
return [_.isUndefined(path) ? changes : path, key]
}
const walk = (fromObject, toObject, path) => {
for (const key of _.keys(fromObject)) {
const objKeyPair = buildPath(path, fromObject, key);
if (!_.has(toObject, key)) {
objKeyPair[0][objKeyPair[1]] = { from: _.get(fromObject, key) };
}
}
for (const [key, to] of _.entries(toObject)) {
const isLast = _.has(fromObject, key);
const objKeyPair = buildPath(path, fromObject, key);
if (isLast) {
const from = _.get(fromObject, key);
if (!_.isEqual(from, to)) {
if (_.isObjectLike(to) && _.isObjectLike(from)) {
walk(from, to, objKeyPair[0][objKeyPair[1]]);
} else {
objKeyPair[0][objKeyPair[1]] = { __old: from, __new: to };
}
} else {
delete objKeyPair[0][objKeyPair[1]]
}
} else {
objKeyPair[0][objKeyPair[1]] = { to };
}
}
};
walk(fromObject, toObject);
return changes;
}
ty
@Aschen There are a few problems with the code and I have tweaked it some more.
Changes:
'+'
and'-'
andnewValue
, the change is now in the form{[from: <>], [to: <>]}
including both old and new values.objectChanges(['a'], {0: 'b'})
andobjectChanges({0: 'b'}, ['a'])
to show the key differently;_.get([{a:[0,1]}],'0.a.1')
works anyways. (also, the original snippet needs an isArray check in the first half of the code too.keyChanges({a: [0, 1]}, {a: [1]})
->{ 'a.1': '-', 'a[0]': 1 }
.)_.has
with_.hasIn
,_.entries
with_.entriesIn
and etc)Issues:
[]
and{}
are treated the same just like the original code.Also, just as an FYI, you can use
_.mixin
to add the function into lodash.Aschen's example