Skip to content

Commit a347257

Browse files
experimental_customMergeAllOf (rjsf-team#4308)
* experimental_customMergeAllOf draft * Add test for experimental_customMergeAllOf * Add documentation for experimental_customMergeAllOf * Changelog update * Add changelog v2 --------- Co-authored-by: Marek Bodinger <[email protected]>
1 parent 787368f commit a347257

File tree

9 files changed

+298
-56
lines changed

9 files changed

+298
-56
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 5.21.3
20+
21+
## @rjsf/utils
22+
23+
- Added `experimental_customMergeAllOf` option to `retrieveSchema` to allow custom merging of `allOf` schemas
24+
1925
# 5.21.2
2026

2127
## @rjsf/core

packages/core/src/components/Form.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
validationDataMerge,
3434
ValidatorType,
3535
Experimental_DefaultFormStateBehavior,
36+
Experimental_CustomMergeAllOf,
3637
} from '@rjsf/utils';
3738
import _forEach from 'lodash/forEach';
3839
import _get from 'lodash/get';
@@ -196,6 +197,9 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
196197
* `emptyObjectFields`
197198
*/
198199
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
200+
/** Optional function that allows for custom merging of `allOf` schemas
201+
*/
202+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
199203
// Private
200204
/**
201205
* _internalFormWrapper is currently used by the semantic-ui theme to provide a custom wrapper around `<Form />`
@@ -390,12 +394,26 @@ export default class Form<
390394
'experimental_defaultFormStateBehavior' in props
391395
? props.experimental_defaultFormStateBehavior
392396
: this.props.experimental_defaultFormStateBehavior;
397+
const experimental_customMergeAllOf =
398+
'experimental_customMergeAllOf' in props
399+
? props.experimental_customMergeAllOf
400+
: this.props.experimental_customMergeAllOf;
393401
let schemaUtils: SchemaUtilsType<T, S, F> = state.schemaUtils;
394402
if (
395403
!schemaUtils ||
396-
schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema, experimental_defaultFormStateBehavior)
404+
schemaUtils.doesSchemaUtilsDiffer(
405+
props.validator,
406+
rootSchema,
407+
experimental_defaultFormStateBehavior,
408+
experimental_customMergeAllOf
409+
)
397410
) {
398-
schemaUtils = createSchemaUtils<T, S, F>(props.validator, rootSchema, experimental_defaultFormStateBehavior);
411+
schemaUtils = createSchemaUtils<T, S, F>(
412+
props.validator,
413+
rootSchema,
414+
experimental_defaultFormStateBehavior,
415+
experimental_customMergeAllOf
416+
);
399417
}
400418
const formData: T = schemaUtils.getDefaultFormState(schema, inputFormData) as T;
401419
const _retrievedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);

packages/docs/docs/api-reference/form-props.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ The signature and documentation for this property is as follow:
9191
##### computeSkipPopulate <T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>()
9292

9393
A function that determines whether to skip populating the array with default values based on the provided validator, schema, and root schema.
94-
If the function returns `true`, the array will not be populated with default values.
95-
If the function returns `false`, the array will be populated with default values according to the `populate` option.
94+
If the function returns `true`, the array will not be populated with default values.
95+
If the function returns `false`, the array will be populated with default values according to the `populate` option.
9696

9797
###### Parameters
9898

@@ -104,7 +104,6 @@ A function that determines whether to skip populating the array with default val
104104

105105
- boolean: A boolean indicating whether to skip populating the array with default values.
106106

107-
108107
##### Example
109108

110109
```tsx
@@ -252,6 +251,30 @@ render(
252251
);
253252
```
254253

254+
## experimental_customMergeAllOf
255+
256+
The `experimental_customMergeAllOf` function allows you to provide a custom implementation for merging `allOf` schemas. This can be particularly useful in scenarios where the default [json-schema-merge-allof](https://github.com/mokkabonna/json-schema-merge-allof) library becomes a performance bottleneck, especially with large and complex schemas or doesn't satisfy your needs.
257+
258+
By providing your own implementation, you can potentially achieve significant performance improvements. For instance, if your use case only requires a subset of JSON Schema features, you can implement a faster, more tailored merging strategy.
259+
260+
If you're looking for alternative `allOf` merging implementations, you might consider [allof-merge](https://github.com/udamir/allof-merge).
261+
262+
**Warning:** This is an experimental feature. Only use this if you fully understand the implications of custom `allOf` merging and are prepared to handle potential edge cases. Incorrect implementations may lead to unexpected behavior or validation errors.
263+
264+
```tsx
265+
import { Form } from '@rjsf/core';
266+
import validator from '@rjsf/validator-ajv8';
267+
268+
const customMergeAllOf = (schema: RJSFSchema): RJSFSchema => {
269+
// Your custom implementation here
270+
};
271+
272+
render(
273+
<Form schema={schema} validator={validator} experimental_customMergeAllOf={customMergeAllOf} />,
274+
document.getElementById('app')
275+
);
276+
```
277+
255278
## disabled
256279

257280
It's possible to disable the whole form by setting the `disabled` prop. The `disabled` prop is then forwarded down to each field of the form.

packages/utils/src/createSchemaUtils.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import deepEquals from './deepEquals';
22
import {
33
ErrorSchema,
4+
Experimental_CustomMergeAllOf,
45
Experimental_DefaultFormStateBehavior,
56
FormContextType,
67
GlobalUISchemaOptions,
@@ -30,31 +31,36 @@ import {
3031
} from './schema';
3132

3233
/** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such
33-
* that one does not have to explicitly pass the `validator`, `rootSchema`, or `experimental_defaultFormStateBehavior` to each method.
34-
* Since these generally do not change across a `Form`, this allows for providing a simplified set of APIs to the
35-
* `@rjsf/core` components and the various themes as well. This class implements the `SchemaUtilsType` interface.
34+
* that one does not have to explicitly pass the `validator`, `rootSchema`, `experimental_defaultFormStateBehavior` or
35+
* `experimental_customMergeAllOf` to each method. Since these generally do not change across a `Form`, this allows for
36+
* providing a simplified set of APIs to the `@rjsf/core` components and the various themes as well. This class
37+
* implements the `SchemaUtilsType` interface.
3638
*/
3739
class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
3840
implements SchemaUtilsType<T, S, F>
3941
{
4042
rootSchema: S;
4143
validator: ValidatorType<T, S, F>;
4244
experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior;
45+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
4346

4447
/** Constructs the `SchemaUtils` instance with the given `validator` and `rootSchema` stored as instance variables
4548
*
4649
* @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs
4750
* @param rootSchema - The root schema that will be forwarded to all the APIs
4851
* @param experimental_defaultFormStateBehavior - Configuration flags to allow users to override default form state behavior
52+
* @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
4953
*/
5054
constructor(
5155
validator: ValidatorType<T, S, F>,
5256
rootSchema: S,
53-
experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior
57+
experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior,
58+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
5459
) {
5560
this.rootSchema = rootSchema;
5661
this.validator = validator;
5762
this.experimental_defaultFormStateBehavior = experimental_defaultFormStateBehavior;
63+
this.experimental_customMergeAllOf = experimental_customMergeAllOf;
5864
}
5965

6066
/** Returns the `ValidatorType` in the `SchemaUtilsType`
@@ -72,20 +78,23 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
7278
* @param validator - An implementation of the `ValidatorType` interface that will be compared against the current one
7379
* @param rootSchema - The root schema that will be compared against the current one
7480
* @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
81+
* @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
7582
* @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema`
7683
*/
7784
doesSchemaUtilsDiffer(
7885
validator: ValidatorType<T, S, F>,
7986
rootSchema: S,
80-
experimental_defaultFormStateBehavior = {}
87+
experimental_defaultFormStateBehavior = {},
88+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
8189
): boolean {
8290
if (!validator || !rootSchema) {
8391
return false;
8492
}
8593
return (
8694
this.validator !== validator ||
8795
!deepEquals(this.rootSchema, rootSchema) ||
88-
!deepEquals(this.experimental_defaultFormStateBehavior, experimental_defaultFormStateBehavior)
96+
!deepEquals(this.experimental_defaultFormStateBehavior, experimental_defaultFormStateBehavior) ||
97+
this.experimental_customMergeAllOf !== experimental_customMergeAllOf
8998
);
9099
}
91100

@@ -110,7 +119,8 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
110119
formData,
111120
this.rootSchema,
112121
includeUndefinedValues,
113-
this.experimental_defaultFormStateBehavior
122+
this.experimental_defaultFormStateBehavior,
123+
this.experimental_customMergeAllOf
114124
);
115125
}
116126

@@ -234,7 +244,13 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
234244
* @returns - The schema having its conditions, additional properties, references and dependencies resolved
235245
*/
236246
retrieveSchema(schema: S, rawFormData?: T) {
237-
return retrieveSchema<T, S, F>(this.validator, schema, this.rootSchema, rawFormData);
247+
return retrieveSchema<T, S, F>(
248+
this.validator,
249+
schema,
250+
this.rootSchema,
251+
rawFormData,
252+
this.experimental_customMergeAllOf
253+
);
238254
}
239255

240256
/** Sanitize the `data` associated with the `oldSchema` so it is considered appropriate for the `newSchema`. If the
@@ -262,7 +278,16 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
262278
* @returns - The `IdSchema` object for the `schema`
263279
*/
264280
toIdSchema(schema: S, id?: string | null, formData?: T, idPrefix = 'root', idSeparator = '_'): IdSchema<T> {
265-
return toIdSchema<T, S, F>(this.validator, schema, id, this.rootSchema, formData, idPrefix, idSeparator);
281+
return toIdSchema<T, S, F>(
282+
this.validator,
283+
schema,
284+
id,
285+
this.rootSchema,
286+
formData,
287+
idPrefix,
288+
idSeparator,
289+
this.experimental_customMergeAllOf
290+
);
266291
}
267292

268293
/** Generates an `PathSchema` object for the `schema`, recursively
@@ -283,6 +308,7 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
283308
* @param validator - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs
284309
* @param rootSchema - The root schema that will be forwarded to all the APIs
285310
* @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
311+
* @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
286312
* @returns - An implementation of a `SchemaUtilsType` interface
287313
*/
288314
export default function createSchemaUtils<
@@ -292,7 +318,13 @@ export default function createSchemaUtils<
292318
>(
293319
validator: ValidatorType<T, S, F>,
294320
rootSchema: S,
295-
experimental_defaultFormStateBehavior = {}
321+
experimental_defaultFormStateBehavior = {},
322+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
296323
): SchemaUtilsType<T, S, F> {
297-
return new SchemaUtils<T, S, F>(validator, rootSchema, experimental_defaultFormStateBehavior);
324+
return new SchemaUtils<T, S, F>(
325+
validator,
326+
rootSchema,
327+
experimental_defaultFormStateBehavior,
328+
experimental_customMergeAllOf
329+
);
298330
}

packages/utils/src/schema/getDefaultFormState.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import mergeDefaultsWithFormData from '../mergeDefaultsWithFormData';
2020
import mergeObjects from '../mergeObjects';
2121
import mergeSchemas from '../mergeSchemas';
2222
import {
23+
Experimental_CustomMergeAllOf,
2324
Experimental_DefaultFormStateBehavior,
2425
FormContextType,
2526
GenericObjectType,
@@ -156,6 +157,8 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
156157
_recurseList?: string[];
157158
/** Optional configuration object, if provided, allows users to override default form state behavior */
158159
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
160+
/** Optional function that allows for custom merging of `allOf` schemas */
161+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
159162
/** Optional flag, if true, indicates this schema was required in the parent schema. */
160163
required?: boolean;
161164
}
@@ -180,6 +183,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
180183
includeUndefinedValues = false,
181184
_recurseList = [],
182185
experimental_defaultFormStateBehavior = undefined,
186+
experimental_customMergeAllOf = undefined,
183187
required,
184188
} = computeDefaultsProps;
185189
const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
@@ -209,7 +213,15 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
209213
...formData,
210214
...getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults),
211215
};
212-
const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, [], defaultFormData);
216+
const resolvedSchema = resolveDependencies<T, S, F>(
217+
validator,
218+
schema,
219+
rootSchema,
220+
false,
221+
[],
222+
defaultFormData,
223+
experimental_customMergeAllOf
224+
);
213225
schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies
214226
} else if (isFixedItems(schema)) {
215227
defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) =>
@@ -298,6 +310,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
298310
includeUndefinedValues = false,
299311
_recurseList = [],
300312
experimental_defaultFormStateBehavior = undefined,
313+
experimental_customMergeAllOf = undefined,
301314
required,
302315
}: ComputeDefaultsProps<T, S> = {},
303316
defaults?: T | T[] | undefined
@@ -309,7 +322,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
309322
// https://github.com/rjsf-team/react-jsonschema-form/issues/3832
310323
const retrievedSchema =
311324
experimental_defaultFormStateBehavior?.allOf === 'populateDefaults' && ALL_OF_KEY in schema
312-
? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData)
325+
? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData, experimental_customMergeAllOf)
313326
: schema;
314327
const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce(
315328
(acc: GenericObjectType, key: string) => {
@@ -319,6 +332,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
319332
rootSchema,
320333
_recurseList,
321334
experimental_defaultFormStateBehavior,
335+
experimental_customMergeAllOf,
322336
includeUndefinedValues: includeUndefinedValues === true,
323337
parentDefaults: get(defaults, [key]),
324338
rawFormData: get(formData, [key]),
@@ -521,6 +535,7 @@ export function getDefaultBasedOnSchemaType<
521535
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
522536
* false when computing defaults for any nested object properties.
523537
* @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
538+
* @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
524539
* @returns - The resulting `formData` with all the defaults provided
525540
*/
526541
export default function getDefaultFormState<
@@ -533,16 +548,18 @@ export default function getDefaultFormState<
533548
formData?: T,
534549
rootSchema?: S,
535550
includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
536-
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior
551+
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior,
552+
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
537553
) {
538554
if (!isObject(theSchema)) {
539555
throw new Error('Invalid schema: ' + theSchema);
540556
}
541-
const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData);
557+
const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData, experimental_customMergeAllOf);
542558
const defaults = computeDefaults<T, S, F>(validator, schema, {
543559
rootSchema,
544560
includeUndefinedValues,
545561
experimental_defaultFormStateBehavior,
562+
experimental_customMergeAllOf,
546563
rawFormData: formData,
547564
});
548565
if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) {

0 commit comments

Comments
 (0)