@rjsf/utils 5.23.2 → 5.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.js +217 -130
  2. package/dist/index.js.map +4 -4
  3. package/dist/utils.esm.js +217 -130
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +209 -122
  6. package/lib/constIsAjvDataReference.d.ts +9 -0
  7. package/lib/constIsAjvDataReference.js +15 -0
  8. package/lib/constIsAjvDataReference.js.map +1 -0
  9. package/lib/enumOptionsDeselectValue.js +3 -3
  10. package/lib/enumOptionsDeselectValue.js.map +1 -1
  11. package/lib/enumOptionsIsSelected.js +3 -3
  12. package/lib/enumOptionsIsSelected.js.map +1 -1
  13. package/lib/getChangedFields.d.ts +17 -0
  14. package/lib/getChangedFields.js +42 -0
  15. package/lib/getChangedFields.js.map +1 -0
  16. package/lib/index.d.ts +2 -1
  17. package/lib/index.js +2 -1
  18. package/lib/index.js.map +1 -1
  19. package/lib/mergeDefaultsWithFormData.d.ts +5 -1
  20. package/lib/mergeDefaultsWithFormData.js +31 -8
  21. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  22. package/lib/parser/ParserValidator.js +3 -3
  23. package/lib/parser/ParserValidator.js.map +1 -1
  24. package/lib/parser/schemaParser.js +4 -4
  25. package/lib/parser/schemaParser.js.map +1 -1
  26. package/lib/schema/getDefaultFormState.d.ts +17 -2
  27. package/lib/schema/getDefaultFormState.js +85 -31
  28. package/lib/schema/getDefaultFormState.js.map +1 -1
  29. package/lib/schema/retrieveSchema.js +7 -4
  30. package/lib/schema/retrieveSchema.js.map +1 -1
  31. package/lib/schema/toIdSchema.js +2 -2
  32. package/lib/schema/toIdSchema.js.map +1 -1
  33. package/lib/schema/toPathSchema.js +3 -3
  34. package/lib/schema/toPathSchema.js.map +1 -1
  35. package/lib/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +2 -2
  37. package/src/constIsAjvDataReference.ts +17 -0
  38. package/src/enumOptionsDeselectValue.ts +3 -4
  39. package/src/enumOptionsIsSelected.ts +3 -4
  40. package/src/getChangedFields.ts +40 -0
  41. package/src/index.ts +2 -0
  42. package/src/mergeDefaultsWithFormData.ts +42 -10
  43. package/src/parser/ParserValidator.ts +3 -3
  44. package/src/parser/schemaParser.ts +4 -4
  45. package/src/schema/getDefaultFormState.ts +126 -31
  46. package/src/schema/retrieveSchema.ts +8 -5
  47. package/src/schema/toIdSchema.ts +2 -2
  48. package/src/schema/toPathSchema.ts +3 -3
@@ -2,6 +2,7 @@ import get from 'lodash/get';
2
2
 
3
3
  import isObject from './isObject';
4
4
  import { GenericObjectType } from '../src';
5
+ import isNil from 'lodash/isNil';
5
6
 
6
7
  /** Merges the `defaults` object of type `T` into the `formData` of type `T`
7
8
  *
@@ -19,47 +20,78 @@ import { GenericObjectType } from '../src';
19
20
  * @param [formData] - The form data into which the defaults will be merged
20
21
  * @param [mergeExtraArrayDefaults=false] - If true, any additional default array entries are appended onto the formData
21
22
  * @param [defaultSupercedesUndefined=false] - If true, an explicit undefined value will be overwritten by the default value
23
+ * @param [overrideFormDataWithDefaults=false] - If true, the default value will overwrite the form data value. If the value
24
+ * doesn't exist in the default, we take it from formData and in the case where the value is set to undefined in formData.
25
+ * This is useful when we have already merged formData with defaults and want to add an additional field from formData
26
+ * that does not exist in defaults.
22
27
  * @returns - The resulting merged form data with defaults
23
28
  */
24
29
  export default function mergeDefaultsWithFormData<T = any>(
25
30
  defaults?: T,
26
31
  formData?: T,
27
32
  mergeExtraArrayDefaults = false,
28
- defaultSupercedesUndefined = false
33
+ defaultSupercedesUndefined = false,
34
+ overrideFormDataWithDefaults = false
29
35
  ): T | undefined {
30
36
  if (Array.isArray(formData)) {
31
37
  const defaultsArray = Array.isArray(defaults) ? defaults : [];
32
- const mapped = formData.map((value, idx) => {
33
- if (defaultsArray[idx]) {
38
+
39
+ // If overrideFormDataWithDefaults is true, we want to override the formData with the defaults
40
+ const overrideArray = overrideFormDataWithDefaults ? defaultsArray : formData;
41
+ const overrideOppositeArray = overrideFormDataWithDefaults ? formData : defaultsArray;
42
+
43
+ const mapped = overrideArray.map((value, idx) => {
44
+ if (overrideOppositeArray[idx]) {
34
45
  return mergeDefaultsWithFormData<any>(
35
46
  defaultsArray[idx],
36
- value,
47
+ formData[idx],
37
48
  mergeExtraArrayDefaults,
38
- defaultSupercedesUndefined
49
+ defaultSupercedesUndefined,
50
+ overrideFormDataWithDefaults
39
51
  );
40
52
  }
41
53
  return value;
42
54
  });
55
+
43
56
  // Merge any extra defaults when mergeExtraArrayDefaults is true
44
- if (mergeExtraArrayDefaults && mapped.length < defaultsArray.length) {
45
- mapped.push(...defaultsArray.slice(mapped.length));
57
+ // Or when overrideFormDataWithDefaults is true and the default array is shorter than the formData array
58
+ if ((mergeExtraArrayDefaults || overrideFormDataWithDefaults) && mapped.length < overrideOppositeArray.length) {
59
+ mapped.push(...overrideOppositeArray.slice(mapped.length));
46
60
  }
47
61
  return mapped as unknown as T;
48
62
  }
49
63
  if (isObject(formData)) {
50
64
  const acc: { [key in keyof T]: any } = Object.assign({}, defaults); // Prevent mutation of source object.
51
65
  return Object.keys(formData as GenericObjectType).reduce((acc, key) => {
66
+ const keyValue = get(formData, key);
67
+ const keyExistsInDefaults = isObject(defaults) && key in (defaults as GenericObjectType);
68
+ const keyExistsInFormData = key in (formData as GenericObjectType);
52
69
  acc[key as keyof T] = mergeDefaultsWithFormData<T>(
53
70
  defaults ? get(defaults, key) : {},
54
- get(formData, key),
71
+ keyValue,
55
72
  mergeExtraArrayDefaults,
56
- defaultSupercedesUndefined
73
+ defaultSupercedesUndefined,
74
+ // overrideFormDataWithDefaults can be true only when the key value exists in defaults
75
+ // Or if the key value doesn't exist in formData
76
+ overrideFormDataWithDefaults && (keyExistsInDefaults || !keyExistsInFormData)
57
77
  );
58
78
  return acc;
59
79
  }, acc);
60
80
  }
61
- if (defaultSupercedesUndefined && formData === undefined) {
81
+
82
+ /**
83
+ * If the defaultSupercedesUndefined flag is true
84
+ * And formData is set to undefined or null and defaults are defined
85
+ * Or if formData is a number and is NaN return defaults
86
+ * Or if overrideFormDataWithDefaults flag is true and formData is set to not undefined/null return defaults
87
+ */
88
+ if (
89
+ (defaultSupercedesUndefined &&
90
+ ((!isNil(defaults) && isNil(formData)) || (typeof formData === 'number' && isNaN(formData)))) ||
91
+ (overrideFormDataWithDefaults && !isNil(formData))
92
+ ) {
62
93
  return defaults;
63
94
  }
95
+
64
96
  return formData;
65
97
  }
@@ -1,5 +1,4 @@
1
1
  import get from 'lodash/get';
2
- import isEqual from 'lodash/isEqual';
3
2
 
4
3
  import { ID_KEY } from '../constants';
5
4
  import hashForSchema from '../hashForSchema';
@@ -15,6 +14,7 @@ import {
15
14
  ValidationData,
16
15
  ValidatorType,
17
16
  } from '../types';
17
+ import deepEquals from '../deepEquals';
18
18
 
19
19
  /** The type of the map of schema hash to schema
20
20
  */
@@ -67,7 +67,7 @@ export default class ParserValidator<T = any, S extends StrictRJSFSchema = RJSFS
67
67
  const existing = this.schemaMap[key];
68
68
  if (!existing) {
69
69
  this.schemaMap[key] = identifiedSchema;
70
- } else if (!isEqual(existing, identifiedSchema)) {
70
+ } else if (!deepEquals(existing, identifiedSchema)) {
71
71
  console.error('existing schema:', JSON.stringify(existing, null, 2));
72
72
  console.error('new schema:', JSON.stringify(identifiedSchema, null, 2));
73
73
  throw new Error(
@@ -91,7 +91,7 @@ export default class ParserValidator<T = any, S extends StrictRJSFSchema = RJSFS
91
91
  * @throws - Error when the given `rootSchema` differs from the root schema provided during construction
92
92
  */
93
93
  isValid(schema: S, _formData: T, rootSchema: S): boolean {
94
- if (!isEqual(rootSchema, this.rootSchema)) {
94
+ if (!deepEquals(rootSchema, this.rootSchema)) {
95
95
  throw new Error('Unexpectedly calling isValid() with a rootSchema that differs from the construction rootSchema');
96
96
  }
97
97
  this.addSchema(schema, hashForSchema<S>(schema));
@@ -1,10 +1,10 @@
1
1
  import forEach from 'lodash/forEach';
2
- import isEqual from 'lodash/isEqual';
3
2
 
4
3
  import { FormContextType, RJSFSchema, StrictRJSFSchema } from '../types';
5
- import { PROPERTIES_KEY, ITEMS_KEY } from '../constants';
4
+ import { ITEMS_KEY, PROPERTIES_KEY } from '../constants';
6
5
  import ParserValidator, { SchemaMap } from './ParserValidator';
7
- import { retrieveSchemaInternal, resolveAnyOrOneOfSchemas } from '../schema/retrieveSchema';
6
+ import { resolveAnyOrOneOfSchemas, retrieveSchemaInternal } from '../schema/retrieveSchema';
7
+ import deepEquals from '../deepEquals';
8
8
 
9
9
  /** Recursive function used to parse the given `schema` belonging to the `rootSchema`. The `validator` is used to
10
10
  * capture the sub-schemas that the `isValid()` function is called with. For each schema returned by the
@@ -24,7 +24,7 @@ function parseSchema<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
24
24
  ) {
25
25
  const schemas = retrieveSchemaInternal<T, S, F>(validator, schema, rootSchema, undefined, true);
26
26
  schemas.forEach((schema) => {
27
- const sameSchemaIndex = recurseList.findIndex((item) => isEqual(item, schema));
27
+ const sameSchemaIndex = recurseList.findIndex((item) => deepEquals(item, schema));
28
28
  if (sameSchemaIndex === -1) {
29
29
  recurseList.push(schema);
30
30
  const allOptions = resolveAnyOrOneOfSchemas<T, S, F>(validator, schema, rootSchema, true);
@@ -1,15 +1,16 @@
1
1
  import get from 'lodash/get';
2
2
  import isEmpty from 'lodash/isEmpty';
3
+ import { JSONSchema7Object } from 'json-schema';
3
4
 
4
5
  import {
6
+ ALL_OF_KEY,
5
7
  ANY_OF_KEY,
6
8
  CONST_KEY,
7
9
  DEFAULT_KEY,
8
10
  DEPENDENCIES_KEY,
9
- PROPERTIES_KEY,
10
11
  ONE_OF_KEY,
12
+ PROPERTIES_KEY,
11
13
  REF_KEY,
12
- ALL_OF_KEY,
13
14
  } from '../constants';
14
15
  import findSchemaDefinition from '../findSchemaDefinition';
15
16
  import getClosestMatchingOption from './getClosestMatchingOption';
@@ -30,8 +31,12 @@ import {
30
31
  ValidatorType,
31
32
  } from '../types';
32
33
  import isMultiSelect from './isMultiSelect';
34
+ import isSelect from './isSelect';
33
35
  import retrieveSchema, { resolveDependencies } from './retrieveSchema';
34
- import { JSONSchema7Object } from 'json-schema';
36
+ import isConstant from '../isConstant';
37
+ import constIsAjvDataReference from '../constIsAjvDataReference';
38
+ import optionsList from '../optionsList';
39
+ import deepEquals from '../deepEquals';
35
40
 
36
41
  const PRIMITIVE_TYPES = ['string', 'number', 'integer', 'boolean', 'null'];
37
42
 
@@ -115,18 +120,17 @@ function maybeAddDefaultToObject<T = any>(
115
120
  // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
116
121
  obj[key] = computedDefault;
117
122
  } else if (emptyObjectFields !== 'skipDefaults') {
118
- if (isObject(computedDefault)) {
119
- // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
120
- // the field key itself in the `requiredField` list
121
- const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired;
123
+ // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
124
+ // the field key itself in the `requiredField` list
125
+ const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired;
122
126
 
127
+ if (isObject(computedDefault)) {
123
128
  // If emptyObjectFields 'skipEmptyDefaults' store computedDefault if it's a non-empty object(e.g. not {})
124
129
  if (emptyObjectFields === 'skipEmptyDefaults') {
125
130
  if (!isEmpty(computedDefault)) {
126
131
  obj[key] = computedDefault;
127
132
  }
128
- }
129
- // Else store computedDefault if it's a non-empty object(e.g. not {}) and satisfies certain conditions
133
+ } // Else store computedDefault if it's a non-empty object(e.g. not {}) and satisfies certain conditions
130
134
  // Condition 1: If computedDefault is not empty or if the key is a required field
131
135
  // Condition 2: If the parent object is required or emptyObjectFields is not 'populateRequiredDefaults'
132
136
  else if (
@@ -138,11 +142,12 @@ function maybeAddDefaultToObject<T = any>(
138
142
  } else if (
139
143
  // Store computedDefault if it's a defined primitive (e.g., true) and satisfies certain conditions
140
144
  // Condition 1: computedDefault is not undefined
141
- // Condition 2: If emptyObjectFields is 'populateAllDefaults' or 'skipEmptyDefaults) or if the key is a required field
145
+ // Condition 2: If emptyObjectFields is 'populateAllDefaults' or 'skipEmptyDefaults)
146
+ // Or if isSelfOrParentRequired is 'true' and the key is a required field
142
147
  computedDefault !== undefined &&
143
148
  (emptyObjectFields === 'populateAllDefaults' ||
144
149
  emptyObjectFields === 'skipEmptyDefaults' ||
145
- requiredFields.includes(key))
150
+ (isSelfOrParentRequired && requiredFields.includes(key)))
146
151
  ) {
147
152
  obj[key] = computedDefault;
148
153
  }
@@ -169,6 +174,10 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
169
174
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
170
175
  /** Optional flag, if true, indicates this schema was required in the parent schema. */
171
176
  required?: boolean;
177
+ /** Optional flag, if true, It will merge defaults into formData.
178
+ * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid.
179
+ */
180
+ shouldMergeDefaultsIntoFormData?: boolean;
172
181
  }
173
182
 
174
183
  /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into
@@ -193,6 +202,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
193
202
  experimental_defaultFormStateBehavior = undefined,
194
203
  experimental_customMergeAllOf = undefined,
195
204
  required,
205
+ shouldMergeDefaultsIntoFormData = false,
196
206
  } = computeDefaultsProps;
197
207
  const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
198
208
  const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
@@ -203,8 +213,12 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
203
213
  let experimental_dfsb_to_compute = experimental_defaultFormStateBehavior;
204
214
  let updatedRecurseList = _recurseList;
205
215
 
206
- if (schema[CONST_KEY] && experimental_defaultFormStateBehavior?.constAsDefaults !== 'never') {
207
- defaults = schema.const as unknown as T;
216
+ if (
217
+ schema[CONST_KEY] &&
218
+ experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
219
+ !constIsAjvDataReference(schema)
220
+ ) {
221
+ defaults = schema[CONST_KEY] as unknown as T;
208
222
  } else if (isObject(defaults) && isObject(schema.default)) {
209
223
  // For object defaults, only override parent defaults that are defined in
210
224
  // schema.default.
@@ -245,6 +259,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
245
259
  parentDefaults: Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined,
246
260
  rawFormData: formData as T,
247
261
  required,
262
+ shouldMergeDefaultsIntoFormData,
248
263
  })
249
264
  ) as T[];
250
265
  } else if (ONE_OF_KEY in schema) {
@@ -260,13 +275,16 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
260
275
  experimental_dfsb_to_compute?.constAsDefaults === 'skipOneOf'
261
276
  ) {
262
277
  // If we are in a oneOf of a primitive type, then we want to pass constAsDefaults as 'never' for the recursion
263
- experimental_dfsb_to_compute = { ...experimental_dfsb_to_compute, constAsDefaults: 'never' };
278
+ experimental_dfsb_to_compute = {
279
+ ...experimental_dfsb_to_compute,
280
+ constAsDefaults: 'never',
281
+ };
264
282
  }
265
283
  schemaToCompute = oneOf![
266
284
  getClosestMatchingOption<T, S, F>(
267
285
  validator,
268
286
  rootSchema,
269
- isEmpty(formData) ? undefined : formData,
287
+ rawFormData,
270
288
  oneOf as S[],
271
289
  0,
272
290
  discriminator,
@@ -284,7 +302,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
284
302
  getClosestMatchingOption<T, S, F>(
285
303
  validator,
286
304
  rootSchema,
287
- isEmpty(formData) ? undefined : formData,
305
+ rawFormData,
288
306
  anyOf as S[],
289
307
  0,
290
308
  discriminator,
@@ -304,6 +322,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
304
322
  parentDefaults: defaults as T | undefined,
305
323
  rawFormData: formData as T,
306
324
  required,
325
+ shouldMergeDefaultsIntoFormData,
307
326
  });
308
327
  }
309
328
 
@@ -314,7 +333,68 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
314
333
 
315
334
  const defaultBasedOnSchemaType = getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults);
316
335
 
317
- return defaultBasedOnSchemaType ?? defaults;
336
+ let defaultsWithFormData = defaultBasedOnSchemaType ?? defaults;
337
+ // if shouldMergeDefaultsIntoFormData is true, then merge the defaults into the formData.
338
+ if (shouldMergeDefaultsIntoFormData) {
339
+ const { arrayMinItems = {} } = experimental_defaultFormStateBehavior || {};
340
+ const { mergeExtraDefaults } = arrayMinItems;
341
+
342
+ const matchingFormData = ensureFormDataMatchingSchema(
343
+ validator,
344
+ schema,
345
+ rootSchema,
346
+ rawFormData,
347
+ experimental_defaultFormStateBehavior
348
+ );
349
+ if (!isObject(rawFormData)) {
350
+ defaultsWithFormData = mergeDefaultsWithFormData<T>(
351
+ defaultsWithFormData as T,
352
+ matchingFormData as T,
353
+ mergeExtraDefaults,
354
+ true
355
+ ) as T;
356
+ }
357
+ }
358
+
359
+ return defaultsWithFormData;
360
+ }
361
+
362
+ /**
363
+ * Ensure that the formData matches the given schema. If it's not matching in the case of a selectField, we change it to match the schema.
364
+ *
365
+ * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
366
+ * @param schema - The schema for which the formData state is desired
367
+ * @param rootSchema - The root schema, used to primarily to look up `$ref`s
368
+ * @param formData - The current formData
369
+ * @param experimental_defaultFormStateBehavior - Optional configuration object, if provided, allows users to override default form state behavior
370
+ * @returns - valid formData that matches schema
371
+ */
372
+ export function ensureFormDataMatchingSchema<
373
+ T = any,
374
+ S extends StrictRJSFSchema = RJSFSchema,
375
+ F extends FormContextType = any
376
+ >(
377
+ validator: ValidatorType<T, S, F>,
378
+ schema: S,
379
+ rootSchema: S,
380
+ formData: T | undefined,
381
+ experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior
382
+ ): T | T[] | undefined {
383
+ const isSelectField = !isConstant(schema) && isSelect(validator, schema, rootSchema);
384
+ let validFormData: T | T[] | undefined = formData;
385
+ if (isSelectField) {
386
+ const getOptionsList = optionsList(schema);
387
+ const isValid = getOptionsList?.some((option) => deepEquals(option.value, formData));
388
+ validFormData = isValid ? formData : undefined;
389
+ }
390
+
391
+ // Override the formData with the const if the constAsDefaults is set to always
392
+ const constTakesPrecedence = schema[CONST_KEY] && experimental_defaultFormStateBehavior?.constAsDefaults === 'always';
393
+ if (constTakesPrecedence) {
394
+ validFormData = schema.const as T;
395
+ }
396
+
397
+ return validFormData;
318
398
  }
319
399
 
320
400
  /** Computes the default value for objects.
@@ -336,6 +416,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
336
416
  experimental_defaultFormStateBehavior = undefined,
337
417
  experimental_customMergeAllOf = undefined,
338
418
  required,
419
+ shouldMergeDefaultsIntoFormData,
339
420
  }: ComputeDefaultsProps<T, S> = {},
340
421
  defaults?: T | T[] | undefined
341
422
  ): T {
@@ -357,7 +438,8 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
357
438
  const hasParentConst = isObject(parentConst) && (parentConst as JSONSchema7Object)[key] !== undefined;
358
439
  const hasConst =
359
440
  ((isObject(propertySchema) && CONST_KEY in propertySchema) || hasParentConst) &&
360
- experimental_defaultFormStateBehavior?.constAsDefaults !== 'never';
441
+ experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
442
+ !constIsAjvDataReference(propertySchema);
361
443
  // Compute the defaults for this node, with the parent defaults we might
362
444
  // have from a previous run: defaults[key].
363
445
  const computedDefault = computeDefaults<T, S, F>(validator, propertySchema, {
@@ -369,6 +451,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
369
451
  parentDefaults: get(defaults, [key]),
370
452
  rawFormData: get(formData, [key]),
371
453
  required: retrievedSchema.required?.includes(key),
454
+ shouldMergeDefaultsIntoFormData,
372
455
  });
373
456
  maybeAddDefaultToObject<T>(
374
457
  acc,
@@ -413,6 +496,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
413
496
  parentDefaults: get(defaults, [key]),
414
497
  rawFormData: get(formData, [key]),
415
498
  required: retrievedSchema.required?.includes(key),
499
+ shouldMergeDefaultsIntoFormData,
416
500
  });
417
501
  // Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop
418
502
  maybeAddDefaultToObject<T>(
@@ -447,6 +531,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
447
531
  experimental_defaultFormStateBehavior = undefined,
448
532
  experimental_customMergeAllOf = undefined,
449
533
  required,
534
+ shouldMergeDefaultsIntoFormData,
450
535
  }: ComputeDefaultsProps<T, S> = {},
451
536
  defaults?: T | T[] | undefined
452
537
  ): T | T[] | undefined {
@@ -474,6 +559,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
474
559
  experimental_customMergeAllOf,
475
560
  parentDefaults: item,
476
561
  required,
562
+ shouldMergeDefaultsIntoFormData,
477
563
  });
478
564
  }) as T[];
479
565
  }
@@ -493,6 +579,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
493
579
  rawFormData: item,
494
580
  parentDefaults: get(defaults, [idx]),
495
581
  required,
582
+ shouldMergeDefaultsIntoFormData,
496
583
  });
497
584
  }) as T[];
498
585
 
@@ -541,6 +628,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
541
628
  experimental_defaultFormStateBehavior,
542
629
  experimental_customMergeAllOf,
543
630
  required,
631
+ shouldMergeDefaultsIntoFormData,
544
632
  })
545
633
  ) as T[];
546
634
  // then fill up the rest with either the item default or empty, up to minItems
@@ -607,26 +695,33 @@ export default function getDefaultFormState<
607
695
  throw new Error('Invalid schema: ' + theSchema);
608
696
  }
609
697
  const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData, experimental_customMergeAllOf);
698
+
699
+ // Get the computed defaults with 'shouldMergeDefaultsIntoFormData' set to true to merge defaults into formData.
700
+ // This is done when for example the value from formData does not exist in the schema 'enum' property, in such
701
+ // cases we take the value from the defaults because the value from the formData is not valid.
610
702
  const defaults = computeDefaults<T, S, F>(validator, schema, {
611
703
  rootSchema,
612
704
  includeUndefinedValues,
613
705
  experimental_defaultFormStateBehavior,
614
706
  experimental_customMergeAllOf,
615
707
  rawFormData: formData,
708
+ shouldMergeDefaultsIntoFormData: true,
616
709
  });
617
710
 
618
- if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) {
619
- // No form data? Use schema defaults.
620
- return defaults;
621
- }
622
- const { mergeDefaultsIntoFormData, arrayMinItems = {} } = experimental_defaultFormStateBehavior || {};
623
- const { mergeExtraDefaults } = arrayMinItems;
624
- const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined';
625
- if (isObject(formData)) {
626
- return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults, defaultSupercedesUndefined);
627
- }
628
- if (Array.isArray(formData)) {
629
- return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults, defaultSupercedesUndefined);
711
+ // If the formData is an object or an array, add additional properties from formData and override formData with
712
+ // defaults since the defaults are already merged with formData.
713
+ if (isObject(formData) || Array.isArray(formData)) {
714
+ const { mergeDefaultsIntoFormData } = experimental_defaultFormStateBehavior || {};
715
+ const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined';
716
+ const result = mergeDefaultsWithFormData<T | T[]>(
717
+ defaults,
718
+ formData,
719
+ true, // set to true to add any additional default array entries.
720
+ defaultSupercedesUndefined,
721
+ true // set to true to override formData with defaults if they exist.
722
+ );
723
+ return result;
630
724
  }
631
- return formData;
725
+
726
+ return defaults;
632
727
  }
@@ -1,5 +1,4 @@
1
1
  import get from 'lodash/get';
2
- import isEqual from 'lodash/isEqual';
3
2
  import set from 'lodash/set';
4
3
  import times from 'lodash/times';
5
4
  import transform from 'lodash/transform';
@@ -15,10 +14,10 @@ import {
15
14
  ANY_OF_KEY,
16
15
  DEPENDENCIES_KEY,
17
16
  IF_KEY,
17
+ ITEMS_KEY,
18
18
  ONE_OF_KEY,
19
- REF_KEY,
20
19
  PROPERTIES_KEY,
21
- ITEMS_KEY,
20
+ REF_KEY,
22
21
  } from '../constants';
23
22
  import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition';
24
23
  import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
@@ -34,6 +33,7 @@ import {
34
33
  ValidatorType,
35
34
  } from '../types';
36
35
  import getFirstMatchingOption from './getFirstMatchingOption';
36
+ import deepEquals from '../deepEquals';
37
37
 
38
38
  /** Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies
39
39
  * resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the
@@ -256,7 +256,10 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
256
256
  )
257
257
  );
258
258
  const allPermutations = getAllPermutationsOfXxxOf<S>(allOfSchemaElements);
259
- return allPermutations.map((permutation) => ({ ...schema, allOf: permutation }));
259
+ return allPermutations.map((permutation) => ({
260
+ ...schema,
261
+ allOf: permutation,
262
+ }));
260
263
  }
261
264
  // No $ref or dependencies or allOf attribute was found, returning the original schema.
262
265
  return [schema];
@@ -356,7 +359,7 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
356
359
  };
357
360
  }
358
361
 
359
- return isEqual(schema, resolvedSchema) ? schema : resolvedSchema;
362
+ return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema;
360
363
  }
361
364
 
362
365
  /** Creates new 'properties' items for each key in the `formData`
@@ -1,5 +1,4 @@
1
1
  import get from 'lodash/get';
2
- import isEqual from 'lodash/isEqual';
3
2
 
4
3
  import { ALL_OF_KEY, DEPENDENCIES_KEY, ID_KEY, ITEMS_KEY, PROPERTIES_KEY, REF_KEY } from '../constants';
5
4
  import isObject from '../isObject';
@@ -14,6 +13,7 @@ import {
14
13
  } from '../types';
15
14
  import retrieveSchema from './retrieveSchema';
16
15
  import getSchemaType from '../getSchemaType';
16
+ import deepEquals from '../deepEquals';
17
17
 
18
18
  /** An internal helper that generates an `IdSchema` object for the `schema`, recursively with protection against
19
19
  * infinite recursion
@@ -42,7 +42,7 @@ function toIdSchemaInternal<T = any, S extends StrictRJSFSchema = RJSFSchema, F
42
42
  ): IdSchema<T> {
43
43
  if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) {
44
44
  const _schema = retrieveSchema<T, S, F>(validator, schema, rootSchema, formData, experimental_customMergeAllOf);
45
- const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema));
45
+ const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema));
46
46
  if (sameSchemaIndex === -1) {
47
47
  return toIdSchemaInternal<T, S, F>(
48
48
  validator,
@@ -1,11 +1,10 @@
1
1
  import get from 'lodash/get';
2
- import isEqual from 'lodash/isEqual';
3
2
  import set from 'lodash/set';
4
3
 
5
4
  import {
5
+ ADDITIONAL_PROPERTIES_KEY,
6
6
  ALL_OF_KEY,
7
7
  ANY_OF_KEY,
8
- ADDITIONAL_PROPERTIES_KEY,
9
8
  DEPENDENCIES_KEY,
10
9
  ITEMS_KEY,
11
10
  NAME_KEY,
@@ -26,6 +25,7 @@ import {
26
25
  } from '../types';
27
26
  import getClosestMatchingOption from './getClosestMatchingOption';
28
27
  import retrieveSchema from './retrieveSchema';
28
+ import deepEquals from '../deepEquals';
29
29
 
30
30
  /** An internal helper that generates an `PathSchema` object for the `schema`, recursively with protection against
31
31
  * infinite recursion
@@ -50,7 +50,7 @@ function toPathSchemaInternal<T = any, S extends StrictRJSFSchema = RJSFSchema,
50
50
  ): PathSchema<T> {
51
51
  if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) {
52
52
  const _schema = retrieveSchema<T, S, F>(validator, schema, rootSchema, formData, experimental_customMergeAllOf);
53
- const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema));
53
+ const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema));
54
54
  if (sameSchemaIndex === -1) {
55
55
  return toPathSchemaInternal<T, S, F>(
56
56
  validator,