-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimizations to improve performance on complex conditional schemas #2466
Conversation
Fixed issue with arrays Removed only from tests better allOf support better allOf support mend If Then Else , Allof Fixed typo and included .lock.json added support for in if/then/else Added support for const as per rjsf-team#1627 Moved handling of if then / else from SchemaField to utils and tidied up things
…a-form into conditional-support
} finally { | ||
// make sure we remove the rootSchema from the global ajv instance | ||
ajv.removeSchema(ROOT_SCHEMA_PREFIX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the root schema being removed (before my change)?
Since we now need to run the validator frequently to evaluate conditional subschemas, keeping the root schema in the cache is essential for performance, especially for large schemas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've figured it out: this function uses the same ajv instance as validateFormData
, which has some advantages and some drawbacks. I think with this new implementation (not removing the root schema), validation breaks for certain schemas. I'm not happy with the performance of the current implementation, though, so I'll have to figure something else out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One cause of performance issues here is that we can't take advantage of AJV's cache because withIdRefPrefix
creates a new object every time. We see a substantial improvement in performance if we memoize withIdRefPrefix
, because validate
sees the same schema and can use the ajv cache instead of recompiling. I'm not yet sure that memoization is 100% safe here, but it seems like a possible path forward.
Another thing I've noticed: we get different behavior with the current implementation if the root schema has an $id. When an $id is present, I think AJV totally ignores the root schema prefix when we call addSchema(rootSchema, ROOT_SCHEMA_PREFIX)
, so the removeSchema
call never works. Not 100% on this either, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for $id
issues, I've filed #2471, ideally would like to address it here (at the risk of increasing scope of this PR) since how RJSF uses AJV is closely related with adding if/then/else support
}); | ||
}); | ||
|
||
it("handles nested if then else", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1666 didn't handle this case properly. A small change in the resolveSchema
function fixed it (changing if "if" in schema
to while "if" in schema
), and here's a test to prove it.
expect(node.querySelector("select[id=root_city]")).not.eql(null); | ||
}); | ||
|
||
it("handles additionalProperties with if then else", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests that we fixed the issue in this comment: #1666 (comment)
39c012b
to
4cf5f7f
Compare
Converting this PR to a draft because while I think my changes in |
packages/core/src/utils.js
Outdated
import fill from "core-js-pure/features/array/fill"; | ||
import union from "lodash/union"; | ||
import jsonpointer from "jsonpointer"; | ||
import fields from "./components/fields"; | ||
import widgets from "./components/widgets"; | ||
import validateFormData, { isValid } from "./validate"; | ||
import _ from "lodash"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep the build size down, it's preferable to import just the necessary function similarly to line 4.
import _ from "lodash"; | |
import memoize from "lodash/memoize"; |
packages/core/src/utils.js
Outdated
if (schema.hasOwnProperty("$ref")) { | ||
return resolveReference(schema, rootSchema, formData); | ||
} else if (schema.hasOwnProperty("dependencies")) { | ||
const resolvedSchema = resolveDependencies(schema, rootSchema, formData); | ||
return retrieveSchema(resolvedSchema, rootSchema, formData); | ||
} else if (schema.hasOwnProperty("allOf")) { | ||
} else if (schema["allOf"]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the reason for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allOf
may be set to undefined here: https://github.com/nickgros/react-jsonschema-form/blob/5e7a08dce80c0c1c85293ac3465c63d65693c6d4/packages/core/src/utils.js#L767
I think I changed that per a comment on the previous PR. Could use delete
instead and revert this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switching that line to delete
actually fixed the liveOmit
issue that I mentioned here. I'm not sure if it fixed the stack overflow error that you saw.
docs/index.md
Outdated
|
||
For arrays this is not the case. Defining an array, when a parent also defines an array, will be overwritten. This is only true when arrays are used in the same level, for objects within these arrays, they will be deeply merged again. | ||
|
||
## Tips and tricks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what's happening here with these docs changes, they are unrelated to the PR and are also out commented. I guess this should be reverted?
@@ -249,7 +249,8 @@ function SchemaFieldRender(props) { | |||
const FieldTemplate = | |||
uiSchema["ui:FieldTemplate"] || registry.FieldTemplate || DefaultTemplate; | |||
let idSchema = props.idSchema; | |||
const schema = retrieveSchema(props.schema, rootSchema, formData); | |||
var schema = retrieveSchema(props.schema, rootSchema, formData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming all const -> var
related changes are due to the original PR being a bit old. In any case, const is highly preferable to var, so please revert all of these changes.
Thanks for taking a look @jimmycallin!
For my use case, I need the option to keep data as an additional property if its field is removed. As our users fill out the form, we want to be as nondestructive as possible with their data, and give them the opportunity to 'backtrack' without having to re-fill fields that they may have already completed. Shouldn't this concern be handled by
Good catch, and thanks for attaching the stack trace! I'm having trouble reproducing this, but you guided me to find that I can't actually change the data in the "All Of If Then Else" example if Edit: I realize now that unless |
Thanks for that PR @nickgros 🎉 Is there anything I can help you with to be able to complete this feature? Would love to this merged sooner than later |
Added if then else logic to resolve schemas
I need support for if-then-else and it's great to see there's a PR, but appears stalled. Anything I can help with? |
…a-form into conditional-support
…chema-form into conditional-support
I don't yet want to close this because I think there's still some value in my changes, but I think #2506 is a better foundation. I'll see if I can integrate those changes into this PR (and in doing so, see if there's a meaningful difference in behavior between how these handle if/then/else) |
…schema-form into conditional-support
* Added tests Added if then else logic to resolve schemas * Changed resolve method's name * Added $ref tests * Code review changes * Remove only on test, add integration tests from #2466 * Fixed merge order * Update tests method names * Code review changes #2700 Co-authored-by: Juansasa <[email protected]> Co-authored-by: Quang Vu <[email protected]>
…a-form into conditional-support
@nickgros Does it make sense to rethink these changes on top of the v5 beta's |
This is probably not the right way to optimize validation. Closing |
I've picked up from where #1666 has left off, hoping that the work I've done so far will be enough to add this feature.In addition to the changes outlined in #1666, I've tried to address changes requested in that review, and fix a few other issues as well.Here's a link to just the changes that I've personally made since I branched from the work in #1666Since conditional support was added in #2700, this branch only exists to preserve the optimizations I've done to improve performance in complex schemas. We struggle with performance on complex conditional schemas because we re-run validation on every input change to determine if the condition has changed. Presently, I've worked around this with lots of memoization and attempts to get ajv to use its internal cache, but we could probably do this better/smarter than what I've done so far.
Checklist