diff --git a/packages/coreutils/src/json.ts b/packages/coreutils/src/json.ts index 018f3e08b..9116c2909 100644 --- a/packages/coreutils/src/json.ts +++ b/packages/coreutils/src/json.ts @@ -25,7 +25,7 @@ type JSONValue = JSONPrimitive | JSONObject | JSONArray; * A type definition for a JSON object. */ export -interface JSONObject { [key: string]: JSONValue; } +interface JSONObject { [key: string]: JSONValue | undefined; } /** @@ -39,7 +39,7 @@ interface JSONArray extends Array { } * A type definition for a readonly JSON object. */ export -interface ReadonlyJSONObject { readonly [key: string]: ReadonlyJSONValue; } +interface ReadonlyJSONObject { readonly [key: string]: ReadonlyJSONValue | undefined; } /** @@ -220,21 +220,36 @@ namespace JSONExt { // Check for the first object's keys in the second object. for (let key in first) { - if (!(key in second)) { + if (first[key] !== undefined && !(key in second)) { return false; } } // Check for the second object's keys in the first object. for (let key in second) { - if (!(key in first)) { + if (second[key] !== undefined && !(key in first)) { return false; } } // Compare the values for equality. for (let key in first) { - if (!deepEqual(first[key], second[key])) { + // Get the values. + let firstValue = first[key]; + let secondValue = second[key]; + + // If both are undefined, ignore the key. + if (firstValue === undefined && secondValue === undefined) { + continue; + } + + // If only one value is undefined, the objects are not equal. + if (firstValue === undefined || secondValue === undefined) { + return false; + } + + // Compare the values. + if (!deepEqual(firstValue, secondValue)) { return false; } } @@ -260,7 +275,12 @@ namespace JSONExt { function deepObjectCopy(value: any): any { let result: any = {}; for (let key in value) { - result[key] = deepCopy(value[key]); + // Ignore undefined values. + let subvalue = value[key]; + if (subvalue === undefined) { + continue; + } + result[key] = deepCopy(subvalue); } return result; } diff --git a/tests/test-coreutils/src/json.spec.ts b/tests/test-coreutils/src/json.spec.ts index 62dc6c29b..71ae9e9b0 100644 --- a/tests/test-coreutils/src/json.spec.ts +++ b/tests/test-coreutils/src/json.spec.ts @@ -14,6 +14,11 @@ import { } from '@phosphor/coreutils'; +interface IFoo extends JSONObject { + bar?: string; +} + + describe('@phosphor/coreutils', () => { describe('JSONExt', () => { @@ -74,6 +79,15 @@ describe('@phosphor/coreutils', () => { expect(JSONExt.deepEqual({ b: 1 }, { a: 1 })).to.equal(false); }); + it('should handle optional keys', () => { + let a: IFoo = { }; + let b: IFoo = { bar: 'a' }; + let c: IFoo = { bar: undefined }; + expect(JSONExt.deepEqual(a, b)).to.equal(false); + expect(JSONExt.deepEqual(a, c)).to.equal(true); + expect(JSONExt.deepEqual(c, a)).to.equal(true); + }); + }); describe('deepCopy()', () => { @@ -110,6 +124,18 @@ describe('@phosphor/coreutils', () => { expect(v7['c']).to.not.equal(r7['c']); }); + it('should handle optional keys', () => { + let v1: IFoo = { }; + let v2: IFoo = { bar: 'a' }; + let v3: JSONObject = { a: false, b: { bar: undefined }}; + let r1 = JSONExt.deepCopy(v1); + let r2 = JSONExt.deepCopy(v2); + let r3 = JSONExt.deepCopy(v3); + expect(Object.keys(r1).length).to.equal(0); + expect(v2.bar).to.equal(r2.bar); + expect(Object.keys(r3.b).length).to.equal(0); + }); + }); });