Fix optional object with required fields#682
Fix optional object with required fields#682davidMcneil wants to merge 1 commit intorjsf-team:masterfrom davidMcneil:master
Conversation
playground/app.js
Outdated
| }; | ||
|
|
||
| setLiveValidate = ({ formData }) => this.setState({ liveValidate: formData }); | ||
| setValidate = ({ formData }) => this.setState({ ...formData }); |
There was a problem hiding this comment.
Just call setState(formData) here. You may even want to try using onChange={this.setState} later.
src/utils.js
Outdated
| } | ||
| } | ||
| if (objectNeedsTrimmed(result)) { | ||
| result = undefined; |
test/utils_test.js
Outdated
| }); | ||
|
|
||
| describe("trimObject()", () => { | ||
| it("should't mutate the provided objects", () => { |
test/utils_test.js
Outdated
| it("should trim empty object", () => { | ||
| const obj = {}; | ||
| const res = trimObject(obj); | ||
| expect(res).eql(undefined); |
There was a problem hiding this comment.
Please ensure using expect(res).to.be.a("undefined") for better accuracy.
src/utils.js
Outdated
| }, acc); | ||
| } | ||
|
|
||
| function objectNeedsTrimmed(obj) { |
There was a problem hiding this comment.
How about objectNeedsTrimming?
src/utils.js
Outdated
|
|
||
| function objectNeedsTrimmed(obj) { | ||
| // Recursively determine if an object needs to be simplified to `undefined`. | ||
| return Object.keys(obj).every(key => { |
There was a problem hiding this comment.
Shouldn't we use some instead of every here?
There was a problem hiding this comment.
I probably needed to do a better job explaining this in the PR, but we need to convert objects with all of their fields set to undefined to simply undefined and do this recursively. For example, convert
{ "a": true, "b": { "c": undefined, "d": { "e": undefined} } } to {"a": true, "b": undefined}
The reason is, assuming we have a schema where b is optional but its fields are required, if b is set to an object the validator requires all of its required fields to be set. So we must "trim" down its unspecified fields and b itself to simply undefined. The every is requiring all of an objects fields are undefined to do a "trim". I do not believe setting it to some would work because the user would be unable to set the fields of an object (the entire object would keep being set to undefined) unless they were somehow able to simultaneously set all fields at once. Thanks for the feedback!
test/utils_test.js
Outdated
| const res = trimObject(obj); | ||
| expect(res).eql({ a: [] }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
I'd like to see more functional tests added at the component level for this behavior.
src/utils.js
Outdated
| if (result.hasOwnProperty(key) && isObject(result[key])) { | ||
| result[key] = trimObject(result[key]); | ||
| } | ||
| } |
There was a problem hiding this comment.
We may want to benchmark this, but I wonder how much slower would be to just use JSON.parse(JSON.stringify(obj)). I've read that these fns were heavily optimized in modern js engines. That would give us:
- Deep cloning for free,
- Getting rid of the
objectNeedsTrimmedfunction call.
There was a problem hiding this comment.
I am not sure simply calling JSON.parse(JSON.stringify(obj)) would work I tried it and it converts {"a": true, "b": {"c": undefined, "d": {"e": undefined}}} to { "a": true, "b": {"d": {}}} and I believe we would need it to convert it to { "a": true, "b":undefined}. That being said we could now simply remove empty objects and get the same behavior, but we still have to walk through the fields of the object recursively.
glasserc
left a comment
There was a problem hiding this comment.
This looks OK to me. The main concern I have is what happens when we trim a required object, since we do so without any regard to whether the object is required or not. In particular, I think it's impossible in the current iteration to have an empty required object (i.e. a required object full of optional fields) as part of a valid input. In other words, I agree with @n1k0 's suggestion to add more functional tests at the component level.
src/utils.js
Outdated
| result = formData || defaults; | ||
| } | ||
| return formData || defaults; | ||
| return trimFormData(result); |
There was a problem hiding this comment.
This means that we trim defaults, which feels like it could be a bit surprising?
test/utils_test.js
Outdated
| it("should trim field object", () => { | ||
| const obj = { a: true, o: { b: undefined } }; | ||
| const res = trimObject(obj); | ||
| expect(res).eql({ a: true, o: undefined }); |
There was a problem hiding this comment.
This might be a bit pedantic, but why return an object with undefined s in it, and not e.g. {a: true}? (Of course, this would require changes to your implementation.
|
@glasserc You are absolutely right this does not handle the case where we have a required object with all optional fields. With the schema below, assuming neither
I would appreciate any feedback on the "correct/best" way to handle the second state, optional |
|
My only real concern with trimming entire objects is that the error messages make sense. If you have a required object with required fields, I think the error messages belong on the required fields ("phone number is a required field") and not the containing object ("user is a required field"). So for me, I'm most concerned about the 3rd case. As for the second case, I think a user might expect either one depending on the application. I think what we should do here is maintain backwards compatibility (so I don't have a strong opinion on how to implement it. I guess you have to examine both the |
|
I would suggest For case 3, it is the lack of |
|
Any updates on this PR? |
|
@davidMcneil , it looks like you deleted the repository / made it private from which this PR was started from, so I can't review your changes. |
|
I'm closing this PR due to the above issue, but @davidMcneil please feel free to make another PR with your actual changes. |
Reasons for making this change
The library would require an optional object if it had required fields. (#675)
Note, this change does not take into account the HTML5 required property. As such I added a "HTML5 Validation" checkbox to the example app to allow for turning off HTML5 validation.
Checklist
npm run cs-formaton my branch to conform my code to prettier coding style