diff --git a/package-lock.json b/package-lock.json index 1a9c69d9e6..f2f7a5c1ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,9 @@ "packages/validator-ajv8", "packages/snapshot-tests" ], + "dependencies": { + "fast-equals": "^5.0.1" + }, "devDependencies": { "@babel/eslint-parser": "^7.23.10", "@nrwl/nx-cloud": "^15.3.5", @@ -16570,6 +16573,15 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", diff --git a/package.json b/package.json index 5852ea996c..ae92ea4c50 100644 --- a/package.json +++ b/package.json @@ -80,5 +80,8 @@ "packages/validator-ajv8", "packages/snapshot-tests" ], - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", + "dependencies": { + "fast-equals": "^5.0.1" + } } diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index a83a629d8f..44758a4b9f 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -39,7 +39,6 @@ import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; -import fastDeepEqual from 'fast-deep-equal'; import getDefaultRegistry from '../getDefaultRegistry'; @@ -283,7 +282,7 @@ export default class Form< } this.state = this.getStateFromProps(props, props.formData); - if (this.props.onChange && !fastDeepEqual(this.state.formData, this.props.formData)) { + if (this.props.onChange && !deepEquals(this.state.formData, this.props.formData)) { this.props.onChange(this.state); } this.formElement = createRef(); @@ -316,8 +315,8 @@ export default class Form< shouldUpdate: false; } { if (!deepEquals(this.props, prevProps)) { - const isSchemaChanged = !fastDeepEqual(prevProps.schema, this.props.schema); - const isFormDataChanged = !fastDeepEqual(prevProps.formData, this.props.formData); + const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema); + const isFormDataChanged = !deepEquals(prevProps.formData, this.props.formData); const nextState = this.getStateFromProps( this.props, this.props.formData, @@ -361,8 +360,8 @@ export default class Form< const { nextState } = snapshot; if ( - !fastDeepEqual(nextState.formData, this.props.formData) && - !fastDeepEqual(nextState.formData, prevState.formData) && + !deepEquals(nextState.formData, this.props.formData) && + !deepEquals(nextState.formData, prevState.formData) && this.props.onChange ) { this.props.onChange(nextState); diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index 845d45f010..c58b080424 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -4,6 +4,7 @@ import isEmpty from 'lodash/isEmpty'; import omit from 'lodash/omit'; import { ANY_OF_KEY, + deepEquals, ERRORS_KEY, FieldProps, FormContextType, @@ -17,7 +18,6 @@ import { TranslatableString, UiSchema, } from '@rjsf/utils'; -import fastDeepEqual from 'fast-deep-equal'; /** Type used for the state of the `AnyOfField` component */ type AnyOfFieldState = { @@ -67,7 +67,7 @@ class AnyOfField schemaUtils.retrieveSchema(opt, formData)); newState = { selectedOption, retrievedOptions }; } - if (!fastDeepEqual(formData, prevProps.formData) && idSchema.$id === prevProps.idSchema.$id) { + if (!deepEquals(formData, prevProps.formData) && idSchema.$id === prevProps.idSchema.$id) { const { retrievedOptions } = newState; const matchingOption = this.getMatchingOption(selectedOption, formData, retrievedOptions); diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index 6045efe250..068423779c 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -1,5 +1,3 @@ -import fastDeepEqual from 'fast-deep-equal'; - import { ErrorSchema, Experimental_DefaultFormStateBehavior, @@ -29,6 +27,7 @@ import { toIdSchema, toPathSchema, } from './schema'; +import deepEquals from './deepEquals'; /** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such * that one does not have to explicitly pass the `validator`, `rootSchema`, or `experimental_defaultFormStateBehavior` to each method. @@ -85,8 +84,8 @@ class SchemaUtils) => boolean) => { + return (a: any, b: any, _idxA: any, _idxB: any, _parentA: any, _parentB: any, state: State) => { + if (isFunctions(a, b)) { + // Assume all functions are equivalent + // see https://github.com/rjsf-team/react-jsonschema-form/issues/255 + return true; + } + + return comparator(a, b, state); + }; + }, +}); /** Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that * assumes all functions are equivalent. @@ -8,12 +26,8 @@ import isEqualWith from 'lodash/isEqualWith'; * @returns - True if the `a` and `b` are deeply equal, false otherwise */ export default function deepEquals(a: any, b: any): boolean { - return isEqualWith(a, b, (obj: any, other: any) => { - if (typeof obj === 'function' && typeof other === 'function') { - // Assume all functions are equivalent - // see https://github.com/rjsf-team/react-jsonschema-form/issues/255 - return true; - } - return undefined; // fallback to default isEquals behavior - }); + if (isFunctions(a, b)) { + return true; + } + return customDeepEqual(a, b); } diff --git a/packages/utils/src/enumOptionsDeselectValue.ts b/packages/utils/src/enumOptionsDeselectValue.ts index 1cabb53ccc..88384321e5 100644 --- a/packages/utils/src/enumOptionsDeselectValue.ts +++ b/packages/utils/src/enumOptionsDeselectValue.ts @@ -1,7 +1,6 @@ -import fastDeepEqual from 'fast-deep-equal'; - import { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types'; import enumOptionsValueForIndex from './enumOptionsValueForIndex'; +import deepEquals from './deepEquals'; /** Removes the enum option value at the `valueIndex` from the currently `selected` (list of) value(s). If `selected` is * a list, then that list is updated to remove the enum option value with the `valueIndex` in `allEnumOptions`. If it is @@ -22,7 +21,7 @@ export default function enumOptionsDeselectValue['value'] | EnumOptionsType['value'][] | undefined { const value = enumOptionsValueForIndex(valueIndex, allEnumOptions); if (Array.isArray(selected)) { - return selected.filter((v) => !fastDeepEqual(v, value)); + return selected.filter((v) => !deepEquals(v, value)); } - return fastDeepEqual(value, selected) ? undefined : selected; + return deepEquals(value, selected) ? undefined : selected; } diff --git a/packages/utils/src/enumOptionsIsSelected.ts b/packages/utils/src/enumOptionsIsSelected.ts index c5d33ade12..e7c782bade 100644 --- a/packages/utils/src/enumOptionsIsSelected.ts +++ b/packages/utils/src/enumOptionsIsSelected.ts @@ -1,5 +1,4 @@ -import fastDeepEqual from 'fast-deep-equal'; - +import deepEquals from './deepEquals'; import { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types'; /** Determines whether the given `value` is (one of) the `selected` value(s). @@ -13,7 +12,7 @@ export default function enumOptionsIsSelected['value'] | EnumOptionsType['value'][] ) { if (Array.isArray(selected)) { - return selected.some((sel) => fastDeepEqual(sel, value)); + return selected.some((sel) => deepEquals(sel, value)); } - return fastDeepEqual(selected, value); + return deepEquals(selected, value); } diff --git a/packages/utils/src/parser/ParserValidator.ts b/packages/utils/src/parser/ParserValidator.ts index fd3b513d40..d6411a85f7 100644 --- a/packages/utils/src/parser/ParserValidator.ts +++ b/packages/utils/src/parser/ParserValidator.ts @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import fastDeepEqual from 'fast-deep-equal'; import { ID_KEY } from '../constants'; import hashForSchema from '../hashForSchema'; @@ -15,6 +14,7 @@ import { ValidationData, ValidatorType, } from '../types'; +import deepEquals from '../deepEquals'; /** The type of the map of schema hash to schema */ @@ -67,7 +67,7 @@ export default class ParserValidator(schema)); diff --git a/packages/utils/src/parser/schemaParser.ts b/packages/utils/src/parser/schemaParser.ts index 2514a482f3..f7e8ffa538 100644 --- a/packages/utils/src/parser/schemaParser.ts +++ b/packages/utils/src/parser/schemaParser.ts @@ -1,10 +1,10 @@ import forEach from 'lodash/forEach'; -import fastDeepEqual from 'fast-deep-equal'; import { FormContextType, RJSFSchema, StrictRJSFSchema } from '../types'; import { ITEMS_KEY, PROPERTIES_KEY } from '../constants'; import ParserValidator, { SchemaMap } from './ParserValidator'; import { resolveAnyOrOneOfSchemas, retrieveSchemaInternal } from '../schema/retrieveSchema'; +import deepEquals from '../deepEquals'; /** Recursive function used to parse the given `schema` belonging to the `rootSchema`. The `validator` is used to * capture the sub-schemas that the `isValid()` function is called with. For each schema returned by the @@ -24,7 +24,7 @@ function parseSchema(validator, schema, rootSchema, undefined, true); schemas.forEach((schema) => { - const sameSchemaIndex = recurseList.findIndex((item) => fastDeepEqual(item, schema)); + const sameSchemaIndex = recurseList.findIndex((item) => deepEquals(item, schema)); if (sameSchemaIndex === -1) { recurseList.push(schema); const allOptions = resolveAnyOrOneOfSchemas(validator, schema, rootSchema, true); diff --git a/packages/utils/src/schema/retrieveSchema.ts b/packages/utils/src/schema/retrieveSchema.ts index c74690b25c..7a66f47f56 100644 --- a/packages/utils/src/schema/retrieveSchema.ts +++ b/packages/utils/src/schema/retrieveSchema.ts @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import fastDeepEqual from 'fast-deep-equal'; import set from 'lodash/set'; import times from 'lodash/times'; import transform from 'lodash/transform'; @@ -27,6 +26,7 @@ import isObject from '../isObject'; import mergeSchemas from '../mergeSchemas'; import { FormContextType, GenericObjectType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types'; import getFirstMatchingOption from './getFirstMatchingOption'; +import deepEquals from '../deepEquals'; /** Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies * resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the @@ -296,7 +296,7 @@ export function resolveAllReferences( }; } - return fastDeepEqual(schema, resolvedSchema) ? schema : resolvedSchema; + return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema; } /** Creates new 'properties' items for each key in the `formData` diff --git a/packages/utils/src/schema/toIdSchema.ts b/packages/utils/src/schema/toIdSchema.ts index c18724ce84..eaeb8dd5f7 100644 --- a/packages/utils/src/schema/toIdSchema.ts +++ b/packages/utils/src/schema/toIdSchema.ts @@ -1,11 +1,11 @@ import get from 'lodash/get'; -import fastDeepEqual from 'fast-deep-equal'; import { ALL_OF_KEY, DEPENDENCIES_KEY, ID_KEY, ITEMS_KEY, PROPERTIES_KEY, REF_KEY } from '../constants'; import isObject from '../isObject'; import { FormContextType, GenericObjectType, IdSchema, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types'; import retrieveSchema from './retrieveSchema'; import getSchemaType from '../getSchemaType'; +import deepEquals from '../deepEquals'; /** An internal helper that generates an `IdSchema` object for the `schema`, recursively with protection against * infinite recursion @@ -32,7 +32,7 @@ function toIdSchemaInternal { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { const _schema = retrieveSchema(validator, schema, rootSchema, formData); - const sameSchemaIndex = _recurseList.findIndex((item) => fastDeepEqual(item, _schema)); + const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); if (sameSchemaIndex === -1) { return toIdSchemaInternal( validator, diff --git a/packages/utils/src/schema/toPathSchema.ts b/packages/utils/src/schema/toPathSchema.ts index 720e73683c..a33ee03c7a 100644 --- a/packages/utils/src/schema/toPathSchema.ts +++ b/packages/utils/src/schema/toPathSchema.ts @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import fastDeepEqual from 'fast-deep-equal'; import set from 'lodash/set'; import { @@ -18,6 +17,7 @@ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema' import { FormContextType, GenericObjectType, PathSchema, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types'; import getClosestMatchingOption from './getClosestMatchingOption'; import retrieveSchema from './retrieveSchema'; +import deepEquals from '../deepEquals'; /** An internal helper that generates an `PathSchema` object for the `schema`, recursively with protection against * infinite recursion @@ -40,7 +40,7 @@ function toPathSchemaInternal { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { const _schema = retrieveSchema(validator, schema, rootSchema, formData); - const sameSchemaIndex = _recurseList.findIndex((item) => fastDeepEqual(item, _schema)); + const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); if (sameSchemaIndex === -1) { return toPathSchemaInternal( validator, diff --git a/packages/validator-ajv6/src/validator.ts b/packages/validator-ajv6/src/validator.ts index 7ac3ccda40..79c221a246 100644 --- a/packages/validator-ajv6/src/validator.ts +++ b/packages/validator-ajv6/src/validator.ts @@ -2,6 +2,7 @@ import { Ajv, ErrorObject } from 'ajv'; import { createErrorHandler, CustomValidator, + deepEquals, ErrorSchema, ErrorTransformer, FormContextType, @@ -19,7 +20,6 @@ import { ValidatorType, withIdRefPrefix, } from '@rjsf/utils'; -import fastDeepEqual from 'fast-deep-equal'; import { CustomValidatorOptionsType } from './types'; import createAjvInstance from './createAjvInstance'; @@ -170,7 +170,7 @@ export default class AJV6Validator