Skip to content

Commit c6fdb92

Browse files
author
Abdallah Al-Soqatri
committed
Fixed performance issue rjsf-team#4203
1 parent ec932db commit c6fdb92

File tree

4 files changed

+52
-18
lines changed

4 files changed

+52
-18
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 5.18.5
20+
21+
## @rjsf/validator-ajv6
22+
23+
- Improved performance issues with large schema dependencies and oneOf conditions [#4203](https://github.com/rjsf-team/react-jsonschema-form/issues/4203).
24+
25+
## @rjsf/validator-ajv8
26+
27+
- Improved performance issues with large schema dependencies and oneOf conditions [#4203](https://github.com/rjsf-team/react-jsonschema-form/issues/4203).
28+
1929
# 5.18.4
2030

2131
## Dev / docs / playground

packages/validator-ajv6/src/validator.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Ajv, ErrorObject } from 'ajv';
22
import {
33
createErrorHandler,
44
CustomValidator,
5+
deepEquals,
56
ErrorSchema,
67
ErrorTransformer,
78
FormContextType,
@@ -158,6 +159,16 @@ export default class AJV6Validator<T = any, S extends StrictRJSFSchema = RJSFSch
158159
return validationDataMerge<T>({ errors, errorSchema }, userErrorSchema);
159160
}
160161

162+
/**
163+
* This function is called when the root schema changes. It removes the old root schema from the ajv instance and adds the new one.
164+
* @param rootSchema - The root schema used to provide $ref resolutions
165+
*/
166+
handleRootSchemaChange(rootSchema: RJSFSchema): void {
167+
const rootSchemaId = ROOT_SCHEMA_PREFIX;
168+
this.ajv.removeSchema(rootSchemaId);
169+
this.ajv.addSchema(rootSchema, rootSchemaId);
170+
}
171+
161172
/** Validates data against a schema, returning true if the data is valid, or
162173
* false otherwise. If the schema is invalid, then this function will return
163174
* false.
@@ -169,16 +180,21 @@ export default class AJV6Validator<T = any, S extends StrictRJSFSchema = RJSFSch
169180
isValid(schema: RJSFSchema, formData: T | undefined, rootSchema: RJSFSchema) {
170181
try {
171182
// add the rootSchema ROOT_SCHEMA_PREFIX as id.
183+
// if schema validator instance doesn't exist, add it.
184+
// else 'handleRootSchemaChange' should be called if the root schema changes so we don't have to remove and recompile the schema every run.
185+
if (this.ajv.getSchema(ROOT_SCHEMA_PREFIX) === undefined) {
186+
// TODO restore the commented out `if` above when the TODO in the `finally` is completed
187+
this.ajv.addSchema(rootSchema, ROOT_SCHEMA_PREFIX);
188+
} else if (!deepEquals(rootSchema, this.ajv.getSchema(ROOT_SCHEMA_PREFIX)?.schema)) {
189+
this.handleRootSchemaChange(rootSchema);
190+
}
172191
// then rewrite the schema ref's to point to the rootSchema
173192
// this accounts for the case where schema have references to models
174193
// that lives in the rootSchema but not in the schema in question.
175-
const result = this.ajv.addSchema(rootSchema, ROOT_SCHEMA_PREFIX).validate(withIdRefPrefix(schema), formData);
194+
const result = this.ajv.validate(withIdRefPrefix(schema), formData);
176195
return result as boolean;
177196
} catch (e) {
178197
return false;
179-
} finally {
180-
// make sure we remove the rootSchema from the global ajv instance
181-
this.ajv.removeSchema(ROOT_SCHEMA_PREFIX);
182198
}
183199
}
184200
}

packages/validator-ajv8/src/validator.ts

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
22
import {
33
CustomValidator,
4+
deepEquals,
45
ErrorSchema,
56
ErrorTransformer,
67
FormContextType,
@@ -119,6 +120,16 @@ export default class AJV8Validator<T = any, S extends StrictRJSFSchema = RJSFSch
119120
return processRawValidationErrors(this, rawErrors, formData, schema, customValidate, transformErrors, uiSchema);
120121
}
121122

123+
/**
124+
* This function is called when the root schema changes. It removes the old root schema from the ajv instance and adds the new one.
125+
* @param rootSchema - The root schema used to provide $ref resolutions
126+
*/
127+
handleRootSchemaChange(rootSchema: S): void {
128+
const rootSchemaId = rootSchema[ID_KEY] ?? ROOT_SCHEMA_PREFIX;
129+
this.ajv.removeSchema(rootSchemaId);
130+
this.ajv.addSchema(rootSchema, rootSchemaId);
131+
}
132+
122133
/** Validates data against a schema, returning true if the data is valid, or
123134
* false otherwise. If the schema is invalid, then this function will return
124135
* false.
@@ -131,13 +142,17 @@ export default class AJV8Validator<T = any, S extends StrictRJSFSchema = RJSFSch
131142
const rootSchemaId = rootSchema[ID_KEY] ?? ROOT_SCHEMA_PREFIX;
132143
try {
133144
// add the rootSchema ROOT_SCHEMA_PREFIX as id.
145+
// if schema validator instance doesn't exist, add it.
146+
// else 'handleRootSchemaChange' should be called if the root schema changes so we don't have to remove and recompile the schema every run.
147+
if (this.ajv.getSchema(rootSchemaId) === undefined) {
148+
// TODO restore the commented out `if` above when the TODO in the `finally` is completed
149+
this.ajv.addSchema(rootSchema, rootSchemaId);
150+
} else if (!deepEquals(rootSchema, this.ajv.getSchema(rootSchemaId)?.schema)) {
151+
this.handleRootSchemaChange(rootSchema);
152+
}
134153
// then rewrite the schema ref's to point to the rootSchema
135154
// this accounts for the case where schema have references to models
136155
// that lives in the rootSchema but not in the schema in question.
137-
// if (this.ajv.getSchema(rootSchemaId) === undefined) {
138-
// TODO restore the commented out `if` above when the TODO in the `finally` is completed
139-
this.ajv.addSchema(rootSchema, rootSchemaId);
140-
// }
141156
const schemaWithIdRefPrefix = withIdRefPrefix<S>(schema) as S;
142157
const schemaId = schemaWithIdRefPrefix[ID_KEY] ?? hashForSchema(schemaWithIdRefPrefix);
143158
let compiledValidator: ValidateFunction | undefined;
@@ -155,10 +170,6 @@ export default class AJV8Validator<T = any, S extends StrictRJSFSchema = RJSFSch
155170
} catch (e) {
156171
console.warn('Error encountered compiling schema:', e);
157172
return false;
158-
} finally {
159-
// TODO: A function should be called if the root schema changes so we don't have to remove and recompile the schema every run.
160-
// make sure we remove the rootSchema from the global ajv instance
161-
this.ajv.removeSchema(rootSchemaId);
162173
}
163174
}
164175
}

packages/validator-ajv8/test/validator.test.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,9 @@ describe('AJV8Validator', () => {
9999
validator.isValid(schema, formData, rootSchema);
100100

101101
// Root schema is added twice
102-
expect(addSchemaSpy).toHaveBeenCalledTimes(3);
102+
expect(addSchemaSpy).toHaveBeenCalledTimes(2);
103103
expect(addSchemaSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(rootSchema), rootSchema.$id);
104104
expect(addSchemaSpy).toHaveBeenNthCalledWith(2, expect.objectContaining(schema), schema.$id);
105-
expect(addSchemaSpy).toHaveBeenLastCalledWith(expect.objectContaining(rootSchema), rootSchema.$id);
106105
});
107106
it('should fallback to using compile', () => {
108107
const schema: RJSFSchema = {
@@ -594,10 +593,9 @@ describe('AJV8Validator', () => {
594593
validator.isValid(schema, formData, rootSchema);
595594

596595
// Root schema is added twice
597-
expect(addSchemaSpy).toHaveBeenCalledTimes(3);
596+
expect(addSchemaSpy).toHaveBeenCalledTimes(2);
598597
expect(addSchemaSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(rootSchema), rootSchema.$id);
599598
expect(addSchemaSpy).toHaveBeenNthCalledWith(2, expect.objectContaining(schema), schema.$id);
600-
expect(addSchemaSpy).toHaveBeenLastCalledWith(expect.objectContaining(rootSchema), rootSchema.$id);
601599
});
602600
});
603601
describe('validator.toErrorList()', () => {
@@ -1050,10 +1048,9 @@ describe('AJV8Validator', () => {
10501048
validator.isValid(schema, formData, rootSchema);
10511049

10521050
// Root schema is added twice
1053-
expect(addSchemaSpy).toHaveBeenCalledTimes(3);
1051+
expect(addSchemaSpy).toHaveBeenCalledTimes(2);
10541052
expect(addSchemaSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(rootSchema), rootSchema.$id);
10551053
expect(addSchemaSpy).toHaveBeenNthCalledWith(2, expect.objectContaining(schema), schema.$id);
1056-
expect(addSchemaSpy).toHaveBeenLastCalledWith(expect.objectContaining(rootSchema), rootSchema.$id);
10571054
});
10581055
});
10591056
describe('validator.toErrorList()', () => {

0 commit comments

Comments
 (0)