Skip to content

Instantly share code, notes, and snippets.

@kaichii
Forked from tennox/deepDiffObj.js
Created September 27, 2021 02:17
Show Gist options
  • Save kaichii/3f0f7d0eebd19216542fb7901d646a36 to your computer and use it in GitHub Desktop.
Save kaichii/3f0f7d0eebd19216542fb7901d646a36 to your computer and use it in GitHub Desktop.
Deep diff between two object, using lodash
import _ from 'lodash';
/**
* Deep diff between two objects - i.e. an object with the new value of new & changed fields.
* Removed fields will be set as undefined on the result.
* Only plain objects will be deeply compared (@see _.isPlainObject)
*
* Inspired by: https://gist.github.com/Yimiprod/7ee176597fef230d1451#gistcomment-2565071
* This fork: https://gist.github.com/TeNNoX/5125ab5770ba287012316dd62231b764/
*
* @param {Object} base Object to compare with (if falsy we return object)
* @param {Object} object Object compared
* @return {Object} Return a new object who represent the changed & new values
*/
export function static deepDiffObj(base, object) {
if (!object) throw new Error(`The object compared should be an object: ${object}`);
if (!base) return object;
const result = _.transform(object, (result, value, key) => {
if (!_.has(base, key)) result[key] = value; // fix edge case: not defined to explicitly defined as undefined
if (!_.isEqual(value, base[key])) {
result[key] = _.isPlainObject(value) && _.isPlainObject(base[key]) ? this.deepDiffObj(base[key], value) : value;
}
});
// map removed fields to undefined
_.forOwn(base, (value, key) => {
if (!_.has(object, key)) result[key] = undefined;
});
return result;
}
deepDiffObj(
{ a: 1, b: 2, c: 'X', foo: {y:4} },
{ a: 4, b: undefined, d: 'Y', foo: {x: 1} }
)
/* => {
a: 4, 
b: undefined, 
d: 'Y', 
foo: { x: 1, y: undefined }, 
c: undefined
}  */
// Run using mocha
import chai from 'chai';
import { deepDiffObj } from './deepDiffObj';
const should = chai.should();
describe('Utils', function() {
describe('deepDiffObj', function() {
const someDate = new Date();
const someOtherDate = new Date('2020-01-01');
someOtherDate.should.not.equal(someDate);
it('should throw with invalid args', () => {
should.throw(() => deepDiffObj()); // called with args -> error
should.throw(() => deepDiffObj(true)); // called with no object -> error
should.throw(() => deepDiffObj({}));
should.throw(() => deepDiffObj(null, null)); // called with falsy object -> error
should.throw(() => deepDiffObj({}, false));
});
it('should return object when base is falsy', () => {
deepDiffObj(null, { a: 1 }).should.eql({ a: 1 });
deepDiffObj(undefined, { a: 1 }).should.eql({ a: 1 });
deepDiffObj(false, { a: 1 }).should.eql({ a: 1 });
deepDiffObj(false, someDate).should.equal(someDate);
});
it('should return new fields', () => {
deepDiffObj({}, { a: 1 }).should.eql({ a: 1 });
deepDiffObj({}, { a: 'X' }).should.eql({ a: 'X' });
deepDiffObj({}, { a: null }).should.eql({ a: null });
deepDiffObj({}, { a: undefined }).should.eql({ a: undefined }); //edge case: undefined -> undefined is not a change
deepDiffObj({}, { a: someDate }).should.eql({ a: someDate });
deepDiffObj({}, { a: [1, 2, 3] }).should.eql({ a: [1, 2, 3] });
deepDiffObj({}, { a: { b: 2 } }).should.eql({ a: { b: 2 } });
});
it('should not return equal fields', () => {
deepDiffObj({ a: 1 }, { a: 1 }).should.eql({});
deepDiffObj({ a: null }, { a: null }).should.eql({});
deepDiffObj({ a: undefined }, { a: undefined }).should.eql({});
deepDiffObj({ a: someDate }, { a: someDate }).should.eql({});
deepDiffObj({ a: [1, 2, 3] }, { a: [1, 2, 3] }).should.eql({});
deepDiffObj({ a: { b: 2 } }, { a: { b: 2 } }).should.eql({});
});
it('should return changed fields', () => {
deepDiffObj({ a: 1 }, { a: 2 }).should.eql({ a: 2 });
deepDiffObj({ a: 1 }, { a: 0 }).should.eql({ a: 0 });
deepDiffObj({ a: 0.0 }, { a: 3 }).should.eql({ a: 3 });
deepDiffObj({ a: null }, { a: 'X' }).should.eql({ a: 'X' });
deepDiffObj({ a: 'X' }, { a: null }).should.eql({ a: null });
deepDiffObj({ a: undefined }, { a: 'X' }).should.eql({ a: 'X' });
deepDiffObj({ a: 'X' }, { a: undefined }).should.eql({ a: undefined });
deepDiffObj({ a: someDate }, { a: someOtherDate }).should.eql({ a: someOtherDate });
deepDiffObj({ a: [1, 2, 3] }, { a: [1, 2, 3, 4] }).should.eql({ a: [1, 2, 3, 4] });
deepDiffObj({ a: [1, 2, 3, 4] }, { a: [1, 2, 3] }).should.eql({ a: [1, 2, 3] });
});
it('should work for nested fields', () => {
deepDiffObj({}, { a: { b: 1 } }).should.eql({ a: { b: 1 } });
deepDiffObj({ a: {} }, { a: { b: 1 } }).should.eql({ a: { b: 1 } });
deepDiffObj({ a: { b: 1 } }, { a: { b: 1 } }).should.eql({});
deepDiffObj({ a: { b: 1 } }, { a: { b: 2 } }).should.eql({ a: { b: 2 } });
deepDiffObj({ a: { b: 1 } }, { a: { b: 0 } }).should.eql({ a: { b: 0 } });
deepDiffObj({ a: { b: 0 } }, { a: { b: 3.0 } }).should.eql({ a: { b: 3.0 } });
deepDiffObj({ a: { b: null } }, { a: { b: 'X' } }).should.eql({ a: { b: 'X' } });
deepDiffObj({ a: { b: 'X' } }, { a: { b: null } }).should.eql({ a: { b: null } });
deepDiffObj({ a: { b: undefined } }, { a: { b: 'X' } }).should.eql({ a: { b: 'X' } });
deepDiffObj({ a: { b: 'X' } }, { a: { b: undefined } }).should.eql({ a: { b: undefined } });
deepDiffObj({ a: { b: someDate } }, { a: { b: someOtherDate } }).should.eql({ a: { b: someOtherDate } });
deepDiffObj({ a: { b: [1, 2, 3] } }, { a: { b: [1, 2, 3, 4] } }).should.eql({ a: { b: [1, 2, 3, 4] } });
deepDiffObj({ a: { b: [1, 2, 3, 4] } }, { a: { b: [1, 2, 3] } }).should.eql({ a: { b: [1, 2, 3] } });
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment