Skip to content

Commit b281292

Browse files
ankomattphillips
authored andcommitted
Support diffing keys named like Object.prototype properties
Fixes #58. Previously, the code assumed that input objects have a `hasOwnProperty` function, or that at least they will acquire one when passed through `utils.properObject`. However, this assumption is flawed: As noted in issue #58, when given input objects have a property on them called `hasOwnProperty`, it overrides the prototype's function property that the code relied on, causing any diffing function to error out with Uncaught TypeError: r.hasOwnProperty is not a function The solution taken here is to forget about `utils.properObject`, and instead introduce `utils.hasOwnProperty` which uses `Object.prototype.hasOwnProperty.call` instead of assuming anything about the input object, and replacing all direct `inputObject.hasOwnProperty` calls with it instead.
1 parent ca791b3 commit b281292

File tree

8 files changed

+31
-54
lines changed

8 files changed

+31
-54
lines changed

src/added/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { isEmpty, isObject, properObject } from '../utils';
1+
import { isEmpty, isObject, hasOwnProperty } from '../utils';
22

33
const addedDiff = (lhs, rhs) => {
44

55
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};
66

7-
const l = properObject(lhs);
8-
const r = properObject(rhs);
7+
const l = lhs;
8+
const r = rhs;
99

1010
return Object.keys(r).reduce((acc, key) => {
11-
if (l.hasOwnProperty(key)) {
11+
if (hasOwnProperty(l, key)) {
1212
const difference = addedDiff(l[key], r[key]);
1313

1414
if (isObject(difference) && isEmpty(difference)) return acc;

src/arrayDiff/index.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { isDate, isEmpty, isObject, properObject } from '../utils';
1+
import { isDate, isEmpty, isObject, hasOwnProperty } from '../utils';
22

33
const diff = (lhs, rhs) => {
44
if (lhs === rhs) return {}; // equal return no diff
55

66
if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs
77

8-
const l = properObject(lhs);
9-
const r = properObject(rhs);
8+
const l = lhs;
9+
const r = rhs;
1010

1111
const deletedValues = Object.keys(l).reduce((acc, key) => {
12-
return r.hasOwnProperty(key) ? acc : { ...acc, [key]: undefined };
12+
return hasOwnProperty(r, key) ? acc : { ...acc, [key]: undefined };
1313
}, {});
1414

1515
if (isDate(l) || isDate(r)) {
@@ -19,11 +19,11 @@ const diff = (lhs, rhs) => {
1919

2020
if (Array.isArray(r) && Array.isArray(l)) {
2121
const deletedValues = l.reduce((acc, item, index) => {
22-
return r.hasOwnProperty(index) ? acc.concat(item) : acc.concat(undefined);
22+
return hasOwnProperty(r, index) ? acc.concat(item) : acc.concat(undefined);
2323
}, []);
2424

2525
return r.reduce((acc, rightItem, index) => {
26-
if (!deletedValues.hasOwnProperty(index)) {
26+
if (!hasOwnProperty(deletedValues, index)) {
2727
return acc.concat(rightItem);
2828
}
2929

@@ -40,7 +40,7 @@ const diff = (lhs, rhs) => {
4040
}
4141

4242
return Object.keys(r).reduce((acc, key) => {
43-
if (!l.hasOwnProperty(key)) return { ...acc, [key]: r[key] }; // return added r key
43+
if (!hasOwnProperty(l, key)) return { ...acc, [key]: r[key] }; // return added r key
4444

4545
const difference = diff(l[key], r[key]);
4646

src/deleted/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { isEmpty, isObject, properObject } from '../utils';
1+
import { isEmpty, isObject, hasOwnProperty } from '../utils';
22

33
const deletedDiff = (lhs, rhs) => {
44
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};
55

6-
const l = properObject(lhs);
7-
const r = properObject(rhs);
6+
const l = lhs;
7+
const r = rhs;
88

99
return Object.keys(l).reduce((acc, key) => {
10-
if (r.hasOwnProperty(key)) {
10+
if (hasOwnProperty(r, key)) {
1111
const difference = deletedDiff(l[key], r[key]);
1212

1313
if (isObject(difference) && isEmpty(difference)) return acc;

src/diff/index.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { isDate, isEmptyObject, isObject, properObject } from "../utils";
1+
import { isDate, isEmptyObject, isObject, hasOwnProperty } from '../utils';
22

33
const diff = (lhs, rhs) => {
44
if (lhs === rhs) return {}; // equal return no diff
55

66
if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs
77

8-
const l = properObject(lhs);
9-
const r = properObject(rhs);
8+
const l = lhs;
9+
const r = rhs;
1010

1111
const deletedValues = Object.keys(l).reduce((acc, key) => {
12-
return r.hasOwnProperty(key) ? acc : { ...acc, [key]: undefined };
12+
return hasOwnProperty(r, key) ? acc : { ...acc, [key]: undefined };
1313
}, {});
1414

1515
if (isDate(l) || isDate(r)) {
@@ -18,7 +18,7 @@ const diff = (lhs, rhs) => {
1818
}
1919

2020
return Object.keys(r).reduce((acc, key) => {
21-
if (!l.hasOwnProperty(key)) return { ...acc, [key]: r[key] }; // return added r key
21+
if (!hasOwnProperty(l, key)) return { ...acc, [key]: r[key] }; // return added r key
2222

2323
const difference = diff(l[key], r[key]);
2424

src/preserveArray.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isObject } from './utils';
1+
import { isObject, hasOwnProperty } from './utils';
22

33
const getLargerArray = (l, r) => l.length > r.length ? l : r;
44

@@ -16,7 +16,7 @@ const preserve = (diff, left, right) => {
1616
return {
1717
...acc,
1818
[key]: array.reduce((acc2, item, index) => {
19-
if (diff[key].hasOwnProperty(index)) {
19+
if (hasOwnProperty(diff[key], index)) {
2020
acc2[index] = preserve(diff[key][index], leftArray[index], rightArray[index]); // diff recurse and check for nested arrays
2121
return acc2;
2222
}

src/updated/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { isDate, isEmptyObject, isObject, properObject } from "../utils";
1+
import { isDate, isEmptyObject, isObject, hasOwnProperty } from '../utils';
22

33
const updatedDiff = (lhs, rhs) => {
44
if (lhs === rhs) return {};
55

66
if (!isObject(lhs) || !isObject(rhs)) return rhs;
77

8-
const l = properObject(lhs);
9-
const r = properObject(rhs);
8+
const l = lhs;
9+
const r = rhs;
1010

1111
if (isDate(l) || isDate(r)) {
1212
if (l.valueOf() == r.valueOf()) return {};
1313
return r;
1414
}
1515

1616
return Object.keys(r).reduce((acc, key) => {
17-
if (l.hasOwnProperty(key)) {
17+
if (hasOwnProperty(l, key)) {
1818
const difference = updatedDiff(l[key], r[key]);
1919

2020
// If the difference is empty, and the lhs is an empty object or the rhs is not an empty object

src/utils/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export const isDate = (d) => d instanceof Date;
2-
export const isEmpty = (o) => Object.keys(o).length === 0;
3-
export const isObject = (o) => o != null && typeof o === "object";
4-
export const properObject = (o) => (isObject(o) && !o.hasOwnProperty ? { ...o } : o);
1+
export const isDate = d => d instanceof Date;
2+
export const isEmpty = o => Object.keys(o).length === 0;
3+
export const isObject = o => o != null && typeof o === 'object';
4+
export const hasOwnProperty = (o, ...args) => Object.prototype.hasOwnProperty.call(o, ...args)
55
export const isEmptyObject = (o) => isObject(o) && isEmpty(o);

src/utils/index.test.js

+1-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isDate, isEmpty, isObject, properObject } from './';
1+
import { isDate, isEmpty, isObject } from './';
22

33
describe('utils', () => {
44

@@ -70,27 +70,4 @@ describe('utils', () => {
7070
expect(isObject(value)).toBe(false);
7171
});
7272
});
73-
74-
describe('.properObject', () => {
75-
it('returns given object when object has keys and hasOwnProperty function', () => {
76-
const o = { a: 1 };
77-
const a = [1];
78-
expect(properObject(o)).toBe(o);
79-
expect(properObject(a)).toBe(a);
80-
});
81-
82-
it('returns given value when value is not an object', () => {
83-
const o = 'hello';
84-
expect(properObject(o)).toBe(o);
85-
});
86-
87-
it('returns object that has given keys and hasOwnProperty function when given object is created from a null', () => {
88-
const o = Object.create(null);
89-
o.a = 1;
90-
const actual = properObject(o);
91-
expect(actual).toEqual({ a: 1 });
92-
expect(typeof actual.hasOwnProperty === 'function').toBe(true);
93-
expect(actual).not.toBe(o);
94-
});
95-
});
9673
});

0 commit comments

Comments
 (0)