-
-
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); | |
} |
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
Doesn't work with this:
differenceBetweenObjects({a: 2, b: [1,2,3,4], c: {r: 34534}}, {a: 1, b: [1,2,3,4,5]})