@rjsf/utils 6.5.3 → 6.6.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 (134) hide show
  1. package/dist/index.cjs +618 -433
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/utils.esm.js +614 -429
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +623 -438
  6. package/lib/ErrorSchemaBuilder.js.map +1 -1
  7. package/lib/canExpand.js.map +1 -1
  8. package/lib/constIsAjvDataReference.js +1 -1
  9. package/lib/constIsAjvDataReference.js.map +1 -1
  10. package/lib/createSchemaUtils.js +3 -3
  11. package/lib/createSchemaUtils.js.map +1 -1
  12. package/lib/enumOptionSelectedValue.js.map +1 -1
  13. package/lib/enumOptionValueDecoder.js.map +1 -1
  14. package/lib/enumOptionsDeselectValue.js +1 -1
  15. package/lib/enumOptionsDeselectValue.js.map +1 -1
  16. package/lib/enumOptionsIndexForValue.js.map +1 -1
  17. package/lib/enumOptionsIsSelected.js.map +1 -1
  18. package/lib/enumOptionsSelectValue.js +1 -1
  19. package/lib/enumOptionsSelectValue.js.map +1 -1
  20. package/lib/enumOptionsValueForIndex.js +2 -2
  21. package/lib/enumOptionsValueForIndex.js.map +1 -1
  22. package/lib/findSchemaDefinition.js +4 -4
  23. package/lib/findSchemaDefinition.js.map +1 -1
  24. package/lib/getChangedFields.js +3 -3
  25. package/lib/getChangedFields.js.map +1 -1
  26. package/lib/getDiscriminatorFieldFromSchema.js.map +1 -1
  27. package/lib/getOptionMatchingSimpleDiscriminator.js.map +1 -1
  28. package/lib/getTestIds.d.ts +1 -1
  29. package/lib/getTestIds.js +4 -3
  30. package/lib/getTestIds.js.map +1 -1
  31. package/lib/getWidget.js +1 -1
  32. package/lib/getWidget.js.map +1 -1
  33. package/lib/idGenerators.js.map +1 -1
  34. package/lib/index.d.ts +6 -6
  35. package/lib/index.js +6 -6
  36. package/lib/index.js.map +1 -1
  37. package/lib/isFormDataAvailable.js +1 -1
  38. package/lib/isFormDataAvailable.js.map +1 -1
  39. package/lib/isRootSchema.js +1 -1
  40. package/lib/isRootSchema.js.map +1 -1
  41. package/lib/mergeDefaultsWithFormData.js +1 -1
  42. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  43. package/lib/parser/ParserValidator.js +1 -1
  44. package/lib/parser/ParserValidator.js.map +1 -1
  45. package/lib/parser/index.d.ts +1 -1
  46. package/lib/parser/index.js.map +1 -1
  47. package/lib/parser/schemaParser.js +2 -2
  48. package/lib/parser/schemaParser.js.map +1 -1
  49. package/lib/removeOptionalEmptyObjects.d.ts +2 -0
  50. package/lib/removeOptionalEmptyObjects.js +8 -29
  51. package/lib/removeOptionalEmptyObjects.js.map +1 -1
  52. package/lib/schema/findFieldInSchema.js +1 -1
  53. package/lib/schema/findFieldInSchema.js.map +1 -1
  54. package/lib/schema/findSelectedOptionInXxxOf.js +1 -1
  55. package/lib/schema/findSelectedOptionInXxxOf.js.map +1 -1
  56. package/lib/schema/getClosestMatchingOption.js +3 -3
  57. package/lib/schema/getClosestMatchingOption.js.map +1 -1
  58. package/lib/schema/getDefaultFormState.d.ts +4 -4
  59. package/lib/schema/getDefaultFormState.js +17 -22
  60. package/lib/schema/getDefaultFormState.js.map +1 -1
  61. package/lib/schema/getFromSchema.js +1 -1
  62. package/lib/schema/getFromSchema.js.map +1 -1
  63. package/lib/schema/index.d.ts +4 -4
  64. package/lib/schema/index.js +4 -4
  65. package/lib/schema/index.js.map +1 -1
  66. package/lib/schema/isMultiSelect.js.map +1 -1
  67. package/lib/schema/omitExtraData.d.ts +18 -8
  68. package/lib/schema/omitExtraData.js +352 -16
  69. package/lib/schema/omitExtraData.js.map +1 -1
  70. package/lib/schema/retrieveSchema.d.ts +15 -0
  71. package/lib/schema/retrieveSchema.js +36 -13
  72. package/lib/schema/retrieveSchema.js.map +1 -1
  73. package/lib/schema/sanitizeDataForNewSchema.js.map +1 -1
  74. package/lib/schema/shallowAllOfMerge.d.ts +11 -0
  75. package/lib/schema/shallowAllOfMerge.js +18 -0
  76. package/lib/schema/shallowAllOfMerge.js.map +1 -0
  77. package/lib/schema/toPathSchema.d.ts +1 -0
  78. package/lib/schema/toPathSchema.js +2 -1
  79. package/lib/schema/toPathSchema.js.map +1 -1
  80. package/lib/shouldRenderOptionalField.js +1 -1
  81. package/lib/shouldRenderOptionalField.js.map +1 -1
  82. package/lib/toErrorSchema.js.map +1 -1
  83. package/lib/tsconfig.tsbuildinfo +1 -1
  84. package/lib/types.d.ts +4 -1
  85. package/lib/useFileWidgetProps.d.ts +1 -1
  86. package/lib/useFileWidgetProps.js +9 -10
  87. package/lib/useFileWidgetProps.js.map +1 -1
  88. package/lib/withIdRefPrefix.js +1 -1
  89. package/lib/withIdRefPrefix.js.map +1 -1
  90. package/package.json +10 -10
  91. package/src/ErrorSchemaBuilder.ts +1 -1
  92. package/src/canExpand.ts +1 -1
  93. package/src/constIsAjvDataReference.ts +3 -2
  94. package/src/createSchemaUtils.ts +18 -17
  95. package/src/enumOptionSelectedValue.ts +1 -1
  96. package/src/enumOptionValueDecoder.ts +1 -1
  97. package/src/enumOptionsDeselectValue.ts +2 -2
  98. package/src/enumOptionsIndexForValue.ts +1 -1
  99. package/src/enumOptionsIsSelected.ts +1 -1
  100. package/src/enumOptionsSelectValue.ts +3 -2
  101. package/src/enumOptionsValueForIndex.ts +2 -2
  102. package/src/findSchemaDefinition.ts +4 -4
  103. package/src/getChangedFields.ts +4 -3
  104. package/src/getDiscriminatorFieldFromSchema.ts +1 -1
  105. package/src/getOptionMatchingSimpleDiscriminator.ts +1 -0
  106. package/src/getTestIds.ts +4 -3
  107. package/src/getWidget.tsx +2 -2
  108. package/src/idGenerators.ts +1 -1
  109. package/src/index.ts +6 -6
  110. package/src/isFormDataAvailable.ts +1 -1
  111. package/src/isRootSchema.ts +1 -1
  112. package/src/mergeDefaultsWithFormData.ts +2 -2
  113. package/src/parser/ParserValidator.ts +1 -1
  114. package/src/parser/index.ts +1 -1
  115. package/src/parser/schemaParser.ts +3 -3
  116. package/src/removeOptionalEmptyObjects.ts +8 -30
  117. package/src/schema/findFieldInSchema.ts +2 -2
  118. package/src/schema/findSelectedOptionInXxxOf.ts +1 -1
  119. package/src/schema/getClosestMatchingOption.ts +4 -4
  120. package/src/schema/getDefaultFormState.ts +18 -24
  121. package/src/schema/getFromSchema.ts +2 -2
  122. package/src/schema/index.ts +5 -3
  123. package/src/schema/isMultiSelect.ts +0 -1
  124. package/src/schema/omitExtraData.ts +398 -19
  125. package/src/schema/retrieveSchema.ts +41 -15
  126. package/src/schema/sanitizeDataForNewSchema.ts +1 -1
  127. package/src/schema/shallowAllOfMerge.ts +19 -0
  128. package/src/schema/toPathSchema.ts +2 -1
  129. package/src/shouldRenderOptionalField.ts +2 -2
  130. package/src/toErrorSchema.ts +1 -1
  131. package/src/tsconfig.json +0 -1
  132. package/src/types.ts +4 -1
  133. package/src/useFileWidgetProps.ts +9 -10
  134. package/src/withIdRefPrefix.ts +2 -1
@@ -1,6 +1,6 @@
1
+ import { JSONSchema7Object } from 'json-schema';
1
2
  import get from 'lodash/get';
2
3
  import isEmpty from 'lodash/isEmpty';
3
- import { JSONSchema7Object } from 'json-schema';
4
4
 
5
5
  import {
6
6
  ALL_OF_KEY,
@@ -13,15 +13,18 @@ import {
13
13
  PROPERTIES_KEY,
14
14
  REF_KEY,
15
15
  } from '../constants';
16
+ import constIsAjvDataReference from '../constIsAjvDataReference';
17
+ import deepEquals from '../deepEquals';
16
18
  import findSchemaDefinition from '../findSchemaDefinition';
17
- import getClosestMatchingOption from './getClosestMatchingOption';
18
19
  import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
19
20
  import getSchemaType from '../getSchemaType';
20
- import isObject from '../isObject';
21
+ import isConstant from '../isConstant';
21
22
  import isFixedItems from '../isFixedItems';
23
+ import isObject from '../isObject';
22
24
  import mergeDefaultsWithFormData from '../mergeDefaultsWithFormData';
23
25
  import mergeObjects from '../mergeObjects';
24
26
  import mergeSchemas from '../mergeSchemas';
27
+ import optionsList from '../optionsList';
25
28
  import {
26
29
  Experimental_CustomMergeAllOf,
27
30
  Experimental_DefaultFormStateBehavior,
@@ -31,13 +34,10 @@ import {
31
34
  StrictRJSFSchema,
32
35
  ValidatorType,
33
36
  } from '../types';
37
+ import getClosestMatchingOption from './getClosestMatchingOption';
34
38
  import isMultiSelect from './isMultiSelect';
35
39
  import isSelect from './isSelect';
36
40
  import retrieveSchema, { resolveDependencies } from './retrieveSchema';
37
- import isConstant from '../isConstant';
38
- import constIsAjvDataReference from '../constIsAjvDataReference';
39
- import optionsList from '../optionsList';
40
- import deepEquals from '../deepEquals';
41
41
 
42
42
  const PRIMITIVE_TYPES = ['string', 'number', 'integer', 'boolean', 'null'];
43
43
 
@@ -218,7 +218,7 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
218
218
  *
219
219
  * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
220
220
  * @param rawSchema - The schema for which the default state is desired
221
- * @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function
221
+ * @param computeDefaultsProps - Optional props for this function
222
222
  * @returns - The resulting `formData` with all the defaults provided
223
223
  */
224
224
  export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
@@ -465,7 +465,7 @@ export function ensureFormDataMatchingSchema<
465
465
  *
466
466
  * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
467
467
  * @param rawSchema - The schema for which the default state is desired
468
- * @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function
468
+ * @param computeDefaultsProps - Optional props for this function
469
469
  * @param defaults - Optional props for this function
470
470
  * @returns - The default value based on the schema type if they are defined for object or array schemas.
471
471
  */
@@ -594,7 +594,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
594
594
  *
595
595
  * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
596
596
  * @param rawSchema - The schema for which the default state is desired
597
- * @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function
597
+ * @param computeDefaultsProps - Optional props for this function
598
598
  * @param defaults - Optional props for this function
599
599
  * @returns - The default value based on the schema type if they are defined for object or array schemas.
600
600
  */
@@ -673,19 +673,13 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
673
673
  }
674
674
  }
675
675
 
676
- // Check if the schema has a const property defined AND we are supporting const as defaults, then we should always
677
- // return the computedDefault since it's coming from the const.
678
- const hasConst =
679
- isObject(schema) && CONST_KEY in schema && experimental_defaultFormStateBehavior?.constAsDefaults !== 'never';
680
- if (hasConst === false) {
681
- if (neverPopulate) {
682
- return defaults ?? emptyDefault;
683
- }
684
- if (ignoreMinItemsFlagSet && !required) {
685
- // If no form data exists or defaults are set leave the field empty/non-existent, otherwise
686
- // return form data/defaults
687
- return defaults ? defaults : undefined;
688
- }
676
+ if (neverPopulate) {
677
+ return defaults ?? emptyDefault;
678
+ }
679
+ if (ignoreMinItemsFlagSet && !required) {
680
+ // If no form data exists or defaults are set leave the field empty/non-existent, otherwise
681
+ // return form data/defaults
682
+ return defaults ? defaults : undefined;
689
683
  }
690
684
 
691
685
  let arrayDefault: T[] | undefined;
@@ -726,7 +720,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
726
720
  *
727
721
  * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
728
722
  * @param rawSchema - The schema for which the default state is desired
729
- * @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function
723
+ * @param computeDefaultsProps - Optional props for this function
730
724
  * @param defaults - Optional props for this function
731
725
  * @returns - The default value based on the schema type if they are defined for object or array schemas.
732
726
  */
@@ -2,7 +2,7 @@ import get from 'lodash/get';
2
2
  import has from 'lodash/has';
3
3
  import isEmpty from 'lodash/isEmpty';
4
4
 
5
- import retrieveSchema from './retrieveSchema';
5
+ import { REF_KEY } from '../constants';
6
6
  import {
7
7
  Experimental_CustomMergeAllOf,
8
8
  FormContextType,
@@ -11,7 +11,7 @@ import {
11
11
  StrictRJSFSchema,
12
12
  ValidatorType,
13
13
  } from '../types';
14
- import { REF_KEY } from '../constants';
14
+ import retrieveSchema from './retrieveSchema';
15
15
 
16
16
  /** Internal helper function that acts like lodash's `get` but additionally retrieves `$ref`s as needed to get the path
17
17
  * for schemas containing potentially nested `$ref`s.
@@ -1,15 +1,15 @@
1
1
  import findFieldInSchema from './findFieldInSchema';
2
2
  import findSelectedOptionInXxxOf from './findSelectedOptionInXxxOf';
3
+ import getClosestMatchingOption from './getClosestMatchingOption';
3
4
  import getDefaultFormState from './getDefaultFormState';
4
5
  import getDisplayLabel from './getDisplayLabel';
5
- import getClosestMatchingOption from './getClosestMatchingOption';
6
6
  import getFirstMatchingOption from './getFirstMatchingOption';
7
7
  import getFromSchema from './getFromSchema';
8
8
  import isFilesArray from './isFilesArray';
9
9
  import isMultiSelect from './isMultiSelect';
10
10
  import isSelect from './isSelect';
11
- import omitExtraData, { getUsedFormData, getFieldNames } from './omitExtraData';
12
- import retrieveSchema from './retrieveSchema';
11
+ import omitExtraData, { getUsedFormData, getFieldNames, isValueEmpty } from './omitExtraData';
12
+ import retrieveSchema, { relaxOptionsForScoring } from './retrieveSchema';
13
13
  import sanitizeDataForNewSchema from './sanitizeDataForNewSchema';
14
14
  import toPathSchema from './toPathSchema';
15
15
 
@@ -26,7 +26,9 @@ export {
26
26
  isFilesArray,
27
27
  isMultiSelect,
28
28
  isSelect,
29
+ isValueEmpty,
29
30
  omitExtraData,
31
+ relaxOptionsForScoring,
30
32
  retrieveSchema,
31
33
  sanitizeDataForNewSchema,
32
34
  toPathSchema,
@@ -1,5 +1,4 @@
1
1
  import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType, Experimental_CustomMergeAllOf } from '../types';
2
-
3
2
  import isSelect from './isSelect';
4
3
 
5
4
  /** Checks to see if the `schema` combination represents a multi-select
@@ -1,11 +1,26 @@
1
- import pick from 'lodash/pick';
2
- import isEmpty from 'lodash/isEmpty';
3
1
  import get from 'lodash/get';
2
+ import isEmpty from 'lodash/isEmpty';
3
+ import isNil from 'lodash/isNil';
4
+ import pick from 'lodash/pick';
4
5
 
5
6
  import { NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG } from '../constants';
6
- import { GenericObjectType, PathSchema, FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
7
- import retrieveSchema from './retrieveSchema';
8
- import toPathSchema from './toPathSchema';
7
+ import findSchemaDefinition from '../findSchemaDefinition';
8
+ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
9
+ import getSchemaType from '../getSchemaType';
10
+ import isObject from '../isObject';
11
+ import {
12
+ Experimental_CustomMergeAllOf,
13
+ FormContextType,
14
+ GenericObjectType,
15
+ PathSchema,
16
+ RJSFSchema,
17
+ StrictRJSFSchema,
18
+ ValidatorType,
19
+ } from '../types';
20
+ import getClosestMatchingOption from './getClosestMatchingOption';
21
+ import isSelect from './isSelect';
22
+ import { relaxOptionsForScoring, resolveAllReferences } from './retrieveSchema';
23
+ import shallowAllOfMerge from './shallowAllOfMerge';
9
24
 
10
25
  /** Returns the `formData` with only the elements specified in the `fields` list
11
26
  *
@@ -68,26 +83,390 @@ export function getFieldNames<T = any>(pathSchema: PathSchema<T>, formData?: T):
68
83
  return getAllPaths(pathSchema);
69
84
  }
70
85
 
71
- /** Takes a `schema` and `formData` and returns a copy of the formData with any fields not defined in the schema removed.
72
- * This is useful for ensuring that only data that is relevant to the schema is preserved. Objects with
73
- * `additionalProperties` keyword set to `true` will not have their extra fields removed.
86
+ /** Returns true when a form value is considered empty: null/undefined/'', an empty array, or a plain
87
+ * object whose every own value is itself empty (recursive). Scalars like `0` and `false` are not empty.
88
+ *
89
+ * @param value - The value to check
90
+ * @returns - True if the value is considered empty, false otherwise
91
+ */
92
+ export function isValueEmpty(value: unknown): boolean {
93
+ if (isNil(value) || value === '') {
94
+ return true;
95
+ }
96
+ if (Array.isArray(value)) {
97
+ return value.length === 0;
98
+ }
99
+ if (isObject(value)) {
100
+ return Object.values(value as GenericObjectType).every(isValueEmpty);
101
+ }
102
+ return false;
103
+ }
104
+
105
+ /** Merges an `allOf` schema into a single flat schema, delegating to `experimental_customMergeAllOf`
106
+ * when provided or falling back to the module-level `shallowAllOfMerge` otherwise.
107
+ *
108
+ * @param schema - A schema containing an `allOf` array to be merged
109
+ * @param [experimental_customMergeAllOf] - Optional custom merge function; see `Form` documentation
110
+ * @returns - The merged schema with `allOf` resolved into a single schema object
111
+ */
112
+ function doMergeAllOf<S extends StrictRJSFSchema = RJSFSchema>(
113
+ schema: S,
114
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
115
+ ): S {
116
+ return experimental_customMergeAllOf ? experimental_customMergeAllOf(schema) : (shallowAllOfMerge(schema) as S);
117
+ }
118
+
119
+ /** A recursive, schema-driven filter that walks `schema` and `formData` in lockstep, keeping only
120
+ * values that are described by the schema. Handles `$ref`, `allOf`, `anyOf`, `oneOf`, `if/then/else`,
121
+ * `patternProperties`, `additionalProperties`, `propertyNames`, and `dependencies`. Optional object
122
+ * properties whose schema-filtered content is entirely empty (per `isValueEmpty`) are pruned; required
123
+ * properties and scalar values are always kept when schema-defined.
74
124
  *
75
125
  * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
76
- * @param schema - The schema to use for filtering the formData
77
- * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
126
+ * @param schema - The schema for which to filter the formData
127
+ * @param [rootSchema] - The root schema, used primarily to look up `$ref`s
78
128
  * @param [formData] - The data for the `Form`
79
- * @returns The `formData` after omitting extra data
129
+ * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
130
+ * @returns - The `formData` after omitting extra data, or `undefined` when `formData` is undefined
80
131
  */
81
132
  export default function omitExtraData<
82
133
  T = any,
83
134
  S extends StrictRJSFSchema = RJSFSchema,
84
135
  F extends FormContextType = any,
85
- >(validator: ValidatorType<T, S, F>, schema: S, rootSchema: S = {} as S, formData?: T): T | undefined {
86
- const retrievedSchema = retrieveSchema(validator, schema, rootSchema, formData);
87
- const pathSchema = toPathSchema(validator, retrievedSchema, '', rootSchema, formData);
88
- const fieldNames = getFieldNames(pathSchema, formData);
89
- const lodashFieldNames = fieldNames.map((fieldPaths: string[]) =>
90
- Array.isArray(fieldPaths) ? fieldPaths.join('.') : fieldPaths,
91
- );
92
- return getUsedFormData(formData, lodashFieldNames);
136
+ >(
137
+ validator: ValidatorType<T, S, F>,
138
+ schema: S,
139
+ rootSchema: S = {} as S,
140
+ formData?: T,
141
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
142
+ ): T | undefined {
143
+ /** Type predicate that narrows `value` to `GenericObjectType` — true when `value` is a plain,
144
+ * non-array object (i.e. a JSON object). Used to distinguish JSON objects from arrays and primitives.
145
+ *
146
+ * @param value - The value to check
147
+ * @returns - True if `value` is a plain non-array object
148
+ */
149
+ function isObjectValue(value: unknown): value is GenericObjectType {
150
+ return isObject(value);
151
+ }
152
+
153
+ /** Type predicate that narrows a `S | boolean` schema definition to `S` — true when `schemaDef` is
154
+ * a schema object rather than a JSON Schema boolean shorthand (`true` meaning allow-all, `false`
155
+ * meaning deny-all).
156
+ *
157
+ * @param schemaDef - The schema definition to check
158
+ * @returns - True if `schemaDef` is a schema object
159
+ */
160
+ function isSchemaObj(schemaDef: S | boolean): schemaDef is S {
161
+ return isObject(schemaDef);
162
+ }
163
+
164
+ /** Copies schema-defined properties from `source` into `target`, applying `omit` recursively for
165
+ * each value. Handles `properties`, `patternProperties`, `additionalProperties`, and `propertyNames`.
166
+ * Optional object-valued properties are pruned when every key in the filtered result is both
167
+ * optional (per the inner schema's `required`) and empty (per `isValueEmpty`). This preserves
168
+ * optional objects whose required children have empty values, while still dropping objects whose
169
+ * schema-filtered content is entirely optional-and-empty. Required properties and scalar values
170
+ * are always written when defined. Always returns `target` — pruning of the object itself is
171
+ * the caller's responsibility.
172
+ *
173
+ * @param schema - The object schema describing which properties are allowed
174
+ * @param source - The source form data object to read values from
175
+ * @param target - The accumulator object to write filtered values into
176
+ * @returns - `target` after all schema-defined properties have been processed
177
+ */
178
+ function handleObject(schema: S, source: GenericObjectType, target: GenericObjectType): GenericObjectType {
179
+ const { properties, additionalProperties, patternProperties, propertyNames } = schema;
180
+ const requiredSet = new Set((schema.required ?? []) as string[]);
181
+
182
+ /** Recursively omits extra data from `value` via `omit`, then conditionally writes the result to
183
+ * `target[key]`. Optional object-valued properties are dropped when every key in the filtered
184
+ * result is both optional (within the inner schema's `required` list) and has an empty value per
185
+ * `isValueEmpty`. This per-key approach prevents required-but-empty child properties — kept by
186
+ * inner `setProperty` calls — from inadvertently causing the parent optional object to be dropped,
187
+ * while still pruning optional objects whose schema-filtered content is entirely empty. Vacuously
188
+ * true for `{}`, so empty results are always dropped. Scalar and array values are not pruned here.
189
+ *
190
+ * @param key - The property key to write on `target`
191
+ * @param schemaDef - The schema (or boolean shorthand) that governs the value at `key`
192
+ * @param value - The raw source value to filter
193
+ * @param [required=false] - When true the property is never pruned regardless of its filtered value
194
+ */
195
+ function setProperty(key: string, schemaDef: S | boolean, value: unknown, required = false) {
196
+ const v = omit(schemaDef, value, target[key]);
197
+ if (!required && isObject(v)) {
198
+ // Resolve $ref so we can inspect the effective required list for the inner schema.
199
+ let sd = isSchemaObj(schemaDef as S | boolean) ? (schemaDef as S) : ({} as S);
200
+ if (sd.$ref !== undefined) {
201
+ sd = findSchemaDefinition(sd.$ref, rootSchema) as S;
202
+ }
203
+ const innerRequired = new Set((sd.required ?? []) as string[]);
204
+ // Drop this optional object when every key in v is both optional in the inner schema
205
+ // and has an empty value. Vacuously true for {} so empty objects are always dropped.
206
+ const shouldDrop = Object.entries(v as GenericObjectType).every(
207
+ ([k, val]) => !innerRequired.has(k) && isValueEmpty(val),
208
+ );
209
+ if (shouldDrop) {
210
+ return;
211
+ }
212
+ }
213
+ if (v !== undefined) {
214
+ target[key] = v;
215
+ }
216
+ }
217
+
218
+ if (properties !== undefined) {
219
+ for (const [key, schemaDef] of Object.entries(properties)) {
220
+ setProperty(key, schemaDef as S | boolean, source[key], requiredSet.has(key));
221
+ }
222
+ }
223
+
224
+ // Track keys not handled by properties/patterns so additionalProperties can pick them up.
225
+ let patternPropertiesRest: string[] | undefined;
226
+ if (patternProperties !== undefined) {
227
+ patternPropertiesRest = [];
228
+ const patterns = Object.entries(patternProperties).map(([pattern, schemaDef]): [RegExp, S | boolean] => [
229
+ new RegExp(pattern),
230
+ schemaDef as S | boolean,
231
+ ]);
232
+ const knownProperties = new Set(Object.keys(properties ?? {}));
233
+ for (const [key, value] of Object.entries(source)) {
234
+ if (knownProperties.has(key)) {
235
+ continue;
236
+ }
237
+ const matched = patterns.find(([re]) => re.test(key));
238
+ if (matched === undefined) {
239
+ patternPropertiesRest.push(key);
240
+ continue;
241
+ }
242
+ setProperty(key, matched[1], value);
243
+ }
244
+ }
245
+
246
+ // JSON Schema spec: absent additionalProperties defaults to true (allow all extra keys). Here
247
+ // we treat it as false so omitExtraData never inadvertently passes through undescribed keys.
248
+ if (additionalProperties !== undefined && additionalProperties !== false) {
249
+ const addlSchema = additionalProperties as S | boolean;
250
+ if (patternPropertiesRest !== undefined) {
251
+ for (const key of patternPropertiesRest) {
252
+ setProperty(key, addlSchema, source[key]);
253
+ }
254
+ } else {
255
+ const knownProperties = new Set(Object.keys(properties ?? {}));
256
+ for (const [key, value] of Object.entries(source)) {
257
+ if (knownProperties.has(key)) {
258
+ continue;
259
+ }
260
+ setProperty(key, addlSchema, value);
261
+ }
262
+ }
263
+ }
264
+
265
+ // When propertyNames is present, the schema only constrains key names — all source keys are valid.
266
+ if (propertyNames !== undefined) {
267
+ for (const [key, value] of Object.entries(source)) {
268
+ target[key] = value;
269
+ }
270
+ }
271
+
272
+ return target;
273
+ }
274
+
275
+ /** Filters array elements from `source` into `target` according to `schema.items` and
276
+ * `schema.additionalItems`. For tuple schemas (`items` is an array) each element is filtered by its
277
+ * per-index schema; elements beyond the tuple length are covered by `additionalItems` when present.
278
+ * For list schemas (`items` is a single schema) every element is filtered by that schema.
279
+ *
280
+ * @param schema - The array schema describing `items` and optionally `additionalItems`
281
+ * @param source - The source array to read elements from
282
+ * @param target - The accumulator array to push filtered elements into
283
+ * @returns - `target` after all applicable source elements have been pushed
284
+ */
285
+ function handleArray(schema: S, source: unknown[], target: unknown[]): unknown[] {
286
+ const { items, additionalItems } = schema;
287
+ if (items !== undefined) {
288
+ if (Array.isArray(items)) {
289
+ for (let i = 0; i < items.length; i++) {
290
+ target.push(omit(items[i] as S | boolean, source[i]));
291
+ }
292
+ } else {
293
+ for (let i = 0; i < source.length; i++) {
294
+ target.push(omit(items as S | boolean, source[i]));
295
+ }
296
+ }
297
+ }
298
+ // additionalItems covers tuple items beyond the items array length.
299
+ if (additionalItems) {
300
+ for (let i = target.length; i < source.length; i++) {
301
+ target.push(omit(additionalItems as S | boolean, source[i]));
302
+ }
303
+ }
304
+ return target;
305
+ }
306
+
307
+ /** Applies the `if/then/else` conditional keywords from `schema` to `target`. When `schema.if` is
308
+ * absent the original `target` is returned unchanged. Otherwise the condition is evaluated — using
309
+ * `validator.isValid` for schema conditions or the boolean value directly — and the matching branch
310
+ * (`then` or `else`) is applied via `omit`. When the selected branch is absent, `target` is returned
311
+ * unchanged.
312
+ *
313
+ * @param schema - The schema potentially containing `if`, `then`, and `else` keywords
314
+ * @param source - The current form data value, passed to `validator.isValid` and the branch `omit`
315
+ * @param target - The already-filtered value to merge the branch result into
316
+ * @returns - The result of applying the matched branch, or `target` when no branch applies
317
+ */
318
+ function handleConditions(schema: S, source: unknown, target: unknown): unknown {
319
+ const { if: condition, then, else: otherwise } = schema;
320
+ if (condition === undefined) {
321
+ return target;
322
+ }
323
+ // validator.isValid signature: (schema, formData, rootSchema)
324
+ const isThenBranch = isSchemaObj(condition as S | boolean)
325
+ ? validator.isValid(condition as S, source as T, rootSchema)
326
+ : condition;
327
+ const branch = isThenBranch ? then : otherwise;
328
+ return branch === undefined ? target : omit(branch as S | boolean, source, target);
329
+ }
330
+
331
+ /** Applies the best-matching `oneOf` option to `source`, merging the result into `target`. When
332
+ * `oneOf` is not an array or the schema represents a select widget (enum-driven), `target` is
333
+ * returned unchanged. `additionalProperties: false` is relaxed on each option before scoring so that
334
+ * `getClosestMatchingOption` can validate freely, but the original option schema is used for the
335
+ * actual `omit` call.
336
+ *
337
+ * @param oneOf - The `oneOf` array from the schema, or `undefined`
338
+ * @param schema - The parent schema containing the `oneOf` keyword
339
+ * @param source - The current form data value used to score each option
340
+ * @param target - The already-filtered value to merge the winning option's result into
341
+ * @returns - The result of applying the best-matching option, or `target` when no matching applies
342
+ */
343
+ function handleOneOf(oneOf: S['oneOf'], schema: S, source: unknown, target: unknown): unknown {
344
+ if (!Array.isArray(oneOf) || isSelect(validator, schema, rootSchema, experimental_customMergeAllOf)) {
345
+ return target;
346
+ }
347
+ // Resolve $refs and relax additionalProperties:false → true in one pass for scoring only.
348
+ // The unrelaxed resolved schema is re-derived for the winning option so that omit() still
349
+ // respects additionalProperties:false during filtering.
350
+ const scoringOptions = relaxOptionsForScoring<S>(oneOf as Array<S | boolean>, true, rootSchema);
351
+ const bestIndex = getClosestMatchingOption<T, S, F>(
352
+ validator,
353
+ rootSchema,
354
+ source as T,
355
+ scoringOptions,
356
+ 0,
357
+ getDiscriminatorFieldFromSchema<S>(schema),
358
+ experimental_customMergeAllOf,
359
+ );
360
+ const winning = (oneOf as Array<S | boolean>)[bestIndex];
361
+ // For object options, re-resolve without relaxation so additionalProperties:false is respected.
362
+ // For boolean options, scoringOptions already holds the converted schema (true→{}, false→{not:{}}).
363
+ const resolved: S = isObject(winning)
364
+ ? resolveAllReferences<S>(winning as S, rootSchema, [])
365
+ : scoringOptions[bestIndex];
366
+ return omit(resolved, source, target);
367
+ }
368
+
369
+ /** Applies `anyOf` branches from `schema` to `source`, merging results into `target`. When `anyOf`
370
+ * is absent or not an array, `target` is returned unchanged. For undefined or empty sources (so that
371
+ * defaults can flow through all branches) every branch is applied in sequence. For non-empty sources
372
+ * the best-matching branch is selected via `handleOneOf`.
373
+ *
374
+ * @param schema - The schema potentially containing an `anyOf` keyword
375
+ * @param source - The current form data value; empty or undefined triggers all-branch application
376
+ * @param target - The already-filtered value to merge branch results into
377
+ * @returns - The result after applying the relevant `anyOf` branch(es), or `target` when inapplicable
378
+ */
379
+ function handleAnyOf(schema: S, source: unknown, target: unknown): unknown {
380
+ const { anyOf } = schema;
381
+ if (!Array.isArray(anyOf)) {
382
+ return target;
383
+ }
384
+ // For undefined or empty collections, apply every branch so defaults flow through.
385
+ if (
386
+ source === undefined ||
387
+ (Array.isArray(source) && source.length === 0) ||
388
+ (isObject(source) && Object.keys(source as object).length === 0)
389
+ ) {
390
+ for (const branch of anyOf as Array<S | boolean>) {
391
+ target = omit(branch, source, target);
392
+ }
393
+ return target;
394
+ }
395
+ return handleOneOf(anyOf, schema, source, target);
396
+ }
397
+
398
+ /** Applies schema-based `dependencies` from `schema` to `source`, merging each active dependency's
399
+ * schema into `target` via `omit`. Property dependencies (plain string arrays) are skipped — only
400
+ * schema dependencies are processed. A dependency is considered active when its trigger key is
401
+ * present on `source`.
402
+ *
403
+ * @param schema - The schema potentially containing a `dependencies` keyword
404
+ * @param source - The current form data value; must be a plain object for dependencies to apply
405
+ * @param target - The already-filtered value to merge dependency results into
406
+ * @returns - The result after applying all active schema dependencies, or `target` when inapplicable
407
+ */
408
+ function handleDependencies(schema: S, source: unknown, target: unknown): unknown {
409
+ const { dependencies } = schema;
410
+ if (dependencies === undefined || !isObjectValue(source)) {
411
+ return target;
412
+ }
413
+ for (const [key, deps] of Object.entries(dependencies)) {
414
+ // Skip property dependencies (string arrays); only process schema dependencies.
415
+ if (!(key in source) || Array.isArray(deps)) {
416
+ continue;
417
+ }
418
+ target = omit(deps as S | boolean, source, target);
419
+ }
420
+ return target;
421
+ }
422
+
423
+ /** Core recursive filter. Resolves `$ref`s, merges `allOf`, then delegates to the type-specific
424
+ * handlers (`handleObject`, `handleArray`) and keyword handlers (`handleAnyOf`, `handleOneOf`,
425
+ * `handleConditions`, `handleDependencies`). Returns `undefined` when `source` is undefined or
426
+ * `schemaDef` is `false`; returns `source` unchanged when `schemaDef` is `true` or empty.
427
+ *
428
+ * @param schemaDef - The schema (or boolean shorthand) to filter `source` against
429
+ * @param source - The raw form data value to filter
430
+ * @param [target] - An optional accumulator carrying results from prior oneOf/anyOf processing
431
+ * @returns - The filtered value, or `undefined` when the schema rejects the value
432
+ */
433
+ function omit(schemaDef: S | boolean, source: unknown, target?: unknown): unknown {
434
+ if (source === undefined || schemaDef === false) {
435
+ return undefined;
436
+ }
437
+ if (schemaDef === true || isEmpty(schemaDef as object)) {
438
+ return source;
439
+ }
440
+
441
+ let schema = schemaDef as S;
442
+ const { $ref: ref, allOf } = schema;
443
+
444
+ if (ref !== undefined) {
445
+ return omit(findSchemaDefinition<S>(ref, rootSchema), source, target);
446
+ }
447
+ if (allOf) {
448
+ schema = doMergeAllOf<S>(schema, experimental_customMergeAllOf);
449
+ }
450
+
451
+ target = handleAnyOf(schema, source, handleOneOf(schema.oneOf, schema, source, target));
452
+
453
+ const type = getSchemaType<S>(schema);
454
+ if (type === 'object') {
455
+ if (!isObjectValue(source)) {
456
+ return undefined;
457
+ }
458
+ target = handleObject(schema, source, isObjectValue(target) ? target : {});
459
+ } else if (type === 'array') {
460
+ if (!Array.isArray(source)) {
461
+ return undefined;
462
+ }
463
+ target = handleArray(schema, source, Array.isArray(target) ? target : []);
464
+ } else if (target === undefined) {
465
+ target = source;
466
+ }
467
+
468
+ return handleDependencies(schema, source, handleConditions(schema, source, target));
469
+ }
470
+
471
+ return omit(schema, formData) as T | undefined;
93
472
  }