Skip to content

Commit c33204e

Browse files
ValYouWepicfaace
andauthored
Do not omit nested additionalProperties (rjsf-team#1774)
Fix bug where additional properties are not submitted when nested and omit data enabled Fixes rjsf-team#1497 Co-authored-by: Ashwin Ramaswami <aramaswamis@gmail.com>
1 parent 7c18c75 commit c33204e

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

packages/core/src/components/Form.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,12 @@ export default class Form extends Component {
196196
Object.keys(_obj).forEach(key => {
197197
if (typeof _obj[key] === "object") {
198198
let newPaths = paths.map(path => `${path}.${key}`);
199-
getAllPaths(_obj[key], acc, newPaths);
199+
// If an object is marked with additionalProperties, all its keys are valid
200+
if (_obj[key].__rjsf_additionalProperties && _obj[key].$name !== "") {
201+
acc.push(_obj[key].$name);
202+
} else {
203+
getAllPaths(_obj[key], acc, newPaths);
204+
}
200205
} else if (key === "$name" && _obj[key] !== "") {
201206
paths.forEach(path => {
202207
path = path.replace(/^\./, "");

packages/core/src/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,11 @@ export function toPathSchema(schema, name = "", rootSchema, formData = {}) {
10101010
const _schema = retrieveSchema(schema, rootSchema, formData);
10111011
return toPathSchema(_schema, name, rootSchema, formData);
10121012
}
1013+
1014+
if (schema.hasOwnProperty("additionalProperties")) {
1015+
pathSchema.__rjsf_additionalProperties = true;
1016+
}
1017+
10131018
if (schema.hasOwnProperty("items") && Array.isArray(formData)) {
10141019
formData.forEach((element, i) => {
10151020
pathSchema[i] = toPathSchema(

packages/core/test/Form_test.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,6 +2815,53 @@ describe("Form omitExtraData and liveOmit", () => {
28152815
);
28162816
});
28172817

2818+
it("should get field marked as additionalProperties", () => {
2819+
const schema = {};
2820+
2821+
const formData = {
2822+
extra: {
2823+
foo: "bar",
2824+
},
2825+
level1: {
2826+
level2: "test",
2827+
extra: "foo",
2828+
mixedMap: {
2829+
namedField: "foo",
2830+
key1: "val1",
2831+
},
2832+
},
2833+
level1a: 1.23,
2834+
};
2835+
2836+
const onSubmit = sandbox.spy();
2837+
const { comp } = createFormComponent({
2838+
schema,
2839+
formData,
2840+
onSubmit,
2841+
});
2842+
2843+
const pathSchema = {
2844+
$name: "",
2845+
level1: {
2846+
$name: "level1",
2847+
level2: { $name: "level1.level2" },
2848+
mixedMap: {
2849+
$name: "level1.mixedMap",
2850+
__rjsf_additionalProperties: true,
2851+
namedField: { $name: "level1.mixedMap.namedField" }, // this name should not be returned, as the root object paths should be returned for objects marked with additionalProperties
2852+
},
2853+
},
2854+
level1a: {
2855+
$name: "level1a",
2856+
},
2857+
};
2858+
2859+
const fieldNames = comp.getFieldNames(pathSchema, formData);
2860+
expect(fieldNames.sort()).eql(
2861+
["level1a", "level1.level2", "level1.mixedMap"].sort()
2862+
);
2863+
});
2864+
28182865
it("should get field names from pathSchema with array", () => {
28192866
const schema = {};
28202867

@@ -2992,6 +3039,63 @@ describe("Form omitExtraData and liveOmit", () => {
29923039
});
29933040
});
29943041

3042+
it("should not omit additionalProperties on change with omitExtraData=true and liveOmit=true", () => {
3043+
const omitExtraData = true;
3044+
const liveOmit = true;
3045+
const schema = {
3046+
type: "object",
3047+
properties: {
3048+
foo: { type: "string" },
3049+
bar: { type: "string" },
3050+
add: {
3051+
type: "object",
3052+
additionalProperties: {},
3053+
},
3054+
},
3055+
};
3056+
const formData = { foo: "foo", baz: "baz", add: { prop: 123 } };
3057+
const { node, onChange } = createFormComponent({
3058+
schema,
3059+
formData,
3060+
omitExtraData,
3061+
liveOmit,
3062+
});
3063+
3064+
Simulate.change(node.querySelector("#root_foo"), {
3065+
target: { value: "foobar" },
3066+
});
3067+
3068+
sinon.assert.calledWithMatch(onChange.lastCall, {
3069+
formData: { foo: "foobar", add: { prop: 123 } },
3070+
});
3071+
});
3072+
3073+
it("should rename formData key if key input is renamed in a nested object with omitExtraData=true and liveOmit=true", () => {
3074+
const { node, onChange } = createFormComponent(
3075+
{
3076+
schema: {
3077+
type: "object",
3078+
properties: {
3079+
nested: {
3080+
additionalProperties: { type: "string" },
3081+
},
3082+
},
3083+
},
3084+
formData: { nested: { key1: "value" } },
3085+
},
3086+
{ omitExtraData: true, liveOmit: true }
3087+
);
3088+
3089+
const textNode = node.querySelector("#root-key");
3090+
Simulate.blur(textNode, {
3091+
target: { value: "key1new" },
3092+
});
3093+
3094+
sinon.assert.calledWithMatch(onChange.lastCall, {
3095+
formData: { nested: { key1new: "value" } },
3096+
});
3097+
});
3098+
29953099
describe("Async errors", () => {
29963100
it("should render the async errors", () => {
29973101
const schema = {

0 commit comments

Comments
 (0)