Skip to content

Commit 6f2813c

Browse files
authored
Don't add undefined values to objects when parsing JSON (#2127)
## Summary: Previously, when parsing typed objects from JSON, we would add `[key]: undefined` for any fields that were specified in the parser schema but were absent from the JSON. This was a difference from the behavior of `JSON.parse()` that could cause bugs in some cases (e.g. if a parsed object was spread into another object, it could overwrite existing values with `undefined` when that was not intended). Thus, to better match the behavior of `JSON.parse()`, we now omit `undefined` values from the parsed object if they are not present on the original object. This PR was inspired by test failures on Khan/webapp#28551. I'm making this change out of an abundance of caution. Issue: LEMS-2774 Test plan: `yarn test` Author: benchristel Reviewers: jeremywiebe, handeyeco Required Reviewers: Approved By: jeremywiebe Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x) Pull Request URL: #2127
1 parent 518b005 commit 6f2813c

File tree

4 files changed

+26
-1423
lines changed

4 files changed

+26
-1423
lines changed

.changeset/happy-pets-breathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": minor
3+
---
4+
5+
Avoid adding undefined values to objects parsed from Perseus JSON when properties are missing.

packages/perseus/src/util/parse-perseus-json/general-purpose-parsers/object.test.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import {assertFailure, success} from "../result";
1+
import {assertFailure, assertSuccess, success} from "../result";
22

33
import {array} from "./array";
44
import {defaulted} from "./defaulted";
55
import {number} from "./number";
66
import {object} from "./object";
7+
import {optional} from "./optional";
78
import {string} from "./string";
89
import {anyFailure, ctx, parseFailureWith} from "./test-helpers";
910

@@ -93,4 +94,19 @@ describe("object", () => {
9394

9495
expect(Train({}, ctx())).toEqual(success({boxcars: []}));
9596
});
97+
98+
it("does not include fields not present on the original object", () => {
99+
const Penguin = object({hat: optional(string)});
100+
const result = Penguin({}, ctx());
101+
assertSuccess(result);
102+
expect(result.value).not.toHaveProperty("hat");
103+
});
104+
105+
it("includes `undefined` fields from the original object", () => {
106+
const Penguin = object({hat: optional(string)});
107+
const result = Penguin({hat: undefined}, ctx());
108+
assertSuccess(result);
109+
expect(result.value).toHaveProperty("hat");
110+
expect(result.value.hat).toBe(undefined);
111+
});
96112
});

packages/perseus/src/util/parse-perseus-json/general-purpose-parsers/object.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export function object<S extends ObjectSchema>(
1919
for (const [prop, propParser] of Object.entries(schema)) {
2020
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
2121
if (isSuccess(result)) {
22-
ret[prop] = result.value;
22+
if (result.value !== undefined || prop in rawValue) {
23+
ret[prop] = result.value;
24+
}
2325
} else {
2426
mismatches.push(...result.detail);
2527
}

0 commit comments

Comments
 (0)