From a4bb2bfd8e559b89eeb7ef158999c082da245f52 Mon Sep 17 00:00:00 2001 From: Igor Brasileiro Date: Fri, 13 Sep 2024 16:17:59 -0300 Subject: [PATCH] Revert "feat: improve deepEquals performance (#4292)" (#4300) * Revert "feat: improve deepEquals performance (#4292)" This reverts commit 514ea85826ebfe48f5827b44f9daced5bff0bc47. * package.json --- CHANGELOG.md | 10 ++++ package-lock.json | 10 ---- packages/utils/package.json | 1 - packages/utils/src/createSchemaUtils.ts | 2 +- packages/utils/src/deepEquals.ts | 47 ++++--------------- .../utils/src/enumOptionsDeselectValue.ts | 7 +-- packages/utils/src/enumOptionsIsSelected.ts | 7 +-- packages/utils/src/parser/ParserValidator.ts | 6 +-- packages/utils/src/parser/schemaParser.ts | 8 ++-- packages/utils/src/schema/retrieveSchema.ts | 13 ++--- packages/utils/src/schema/toIdSchema.ts | 4 +- packages/utils/src/schema/toPathSchema.ts | 6 +-- packages/utils/test/deepEquals.test.ts | 2 +- .../src/precompiledValidator.ts | 8 ++-- 14 files changed, 51 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bbb891675..028c86e7a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ should change the heading of the (upcoming) version to include a major version b --> +# 5.21.1 + +## @rjsf/utils + +- Revert of updating `deepEquals()` from [#4292] + +## @validator-ajv8 + +- Revert of using `deepEquals()` instead of `lodash.isEqual()` from [#4292] + # 5.21.0 ## @rjsf/core diff --git a/package-lock.json b/package-lock.json index 6fdbc9e92a..fc8d237c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16648,15 +16648,6 @@ "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", @@ -35245,7 +35236,6 @@ "version": "5.21.0", "license": "Apache-2.0", "dependencies": { - "fast-equals": "^5.0.1", "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", diff --git a/packages/utils/package.json b/packages/utils/package.json index 0583dab49a..45ae499ec1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -36,7 +36,6 @@ "react": "^16.14.0 || >=17" }, "dependencies": { - "fast-equals": "^5.0.1", "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index b7e1c73b06..39e743d945 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -14,9 +14,9 @@ import { ValidatorType, } from './types'; import { - getClosestMatchingOption, getDefaultFormState, getDisplayLabel, + getClosestMatchingOption, getFirstMatchingOption, getMatchingOption, isFilesArray, diff --git a/packages/utils/src/deepEquals.ts b/packages/utils/src/deepEquals.ts index 61aa6fe292..2e2538848e 100644 --- a/packages/utils/src/deepEquals.ts +++ b/packages/utils/src/deepEquals.ts @@ -1,37 +1,6 @@ -import { createCustomEqual, State } from 'fast-equals'; +import isEqualWith from 'lodash/isEqualWith'; -/** Check if all parameters are typeof function. - * - * @param a - The first element to check typeof - * @param b - The second element to check typeof - * @returns - if typeof a and b are equal to function return true, otherwise false - */ -function isFunctions(a: any, b: any) { - return typeof a === 'function' && typeof b === 'function'; -} - -/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that - * assumes all functions in objects are equivalent. - * - * @param a - The first element to compare - * @param b - The second element to compare - * @returns - True if the `a` and `b` are deeply equal, false otherwise - */ -const customDeepEqual = createCustomEqual({ - createInternalComparator: (comparator: (a: any, b: any, state: State) => 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 `fast-equal.createCustomEqual` function, that provides a customized comparator that +/** Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that * assumes all functions are equivalent. * * @param a - The first element to compare @@ -39,8 +8,12 @@ const customDeepEqual = createCustomEqual({ * @returns - True if the `a` and `b` are deeply equal, false otherwise */ export default function deepEquals(a: any, b: any): boolean { - if (isFunctions(a, b)) { - return true; - } - return customDeepEqual(a, b); + 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 + }); } diff --git a/packages/utils/src/enumOptionsDeselectValue.ts b/packages/utils/src/enumOptionsDeselectValue.ts index 88384321e5..1dde198c32 100644 --- a/packages/utils/src/enumOptionsDeselectValue.ts +++ b/packages/utils/src/enumOptionsDeselectValue.ts @@ -1,6 +1,7 @@ +import isEqual from 'lodash/isEqual'; + 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 @@ -21,7 +22,7 @@ export default function enumOptionsDeselectValue['value'] | EnumOptionsType['value'][] | undefined { const value = enumOptionsValueForIndex(valueIndex, allEnumOptions); if (Array.isArray(selected)) { - return selected.filter((v) => !deepEquals(v, value)); + return selected.filter((v) => !isEqual(v, value)); } - return deepEquals(value, selected) ? undefined : selected; + return isEqual(value, selected) ? undefined : selected; } diff --git a/packages/utils/src/enumOptionsIsSelected.ts b/packages/utils/src/enumOptionsIsSelected.ts index e7c782bade..a1c9fed1dd 100644 --- a/packages/utils/src/enumOptionsIsSelected.ts +++ b/packages/utils/src/enumOptionsIsSelected.ts @@ -1,4 +1,5 @@ -import deepEquals from './deepEquals'; +import isEqual from 'lodash/isEqual'; + import { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types'; /** Determines whether the given `value` is (one of) the `selected` value(s). @@ -12,7 +13,7 @@ export default function enumOptionsIsSelected['value'] | EnumOptionsType['value'][] ) { if (Array.isArray(selected)) { - return selected.some((sel) => deepEquals(sel, value)); + return selected.some((sel) => isEqual(sel, value)); } - return deepEquals(selected, value); + return isEqual(selected, value); } diff --git a/packages/utils/src/parser/ParserValidator.ts b/packages/utils/src/parser/ParserValidator.ts index d6411a85f7..f1b771fc39 100644 --- a/packages/utils/src/parser/ParserValidator.ts +++ b/packages/utils/src/parser/ParserValidator.ts @@ -1,4 +1,5 @@ import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; import { ID_KEY } from '../constants'; import hashForSchema from '../hashForSchema'; @@ -14,7 +15,6 @@ 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 f7e8ffa538..70151f06a0 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 isEqual from 'lodash/isEqual'; import { FormContextType, RJSFSchema, StrictRJSFSchema } from '../types'; -import { ITEMS_KEY, PROPERTIES_KEY } from '../constants'; +import { PROPERTIES_KEY, ITEMS_KEY } from '../constants'; import ParserValidator, { SchemaMap } from './ParserValidator'; -import { resolveAnyOrOneOfSchemas, retrieveSchemaInternal } from '../schema/retrieveSchema'; -import deepEquals from '../deepEquals'; +import { retrieveSchemaInternal, resolveAnyOrOneOfSchemas } from '../schema/retrieveSchema'; /** 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) => deepEquals(item, schema)); + const sameSchemaIndex = recurseList.findIndex((item) => isEqual(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 7a66f47f56..0f46632f1c 100644 --- a/packages/utils/src/schema/retrieveSchema.ts +++ b/packages/utils/src/schema/retrieveSchema.ts @@ -1,4 +1,5 @@ import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; import set from 'lodash/set'; import times from 'lodash/times'; import transform from 'lodash/transform'; @@ -14,10 +15,10 @@ import { ANY_OF_KEY, DEPENDENCIES_KEY, IF_KEY, - ITEMS_KEY, ONE_OF_KEY, - PROPERTIES_KEY, REF_KEY, + PROPERTIES_KEY, + ITEMS_KEY, } from '../constants'; import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition'; import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema'; @@ -26,7 +27,6 @@ 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 @@ -196,10 +196,7 @@ export function resolveSchema(allOfSchemaElements); - return allPermutations.map((permutation) => ({ - ...schema, - allOf: permutation, - })); + return allPermutations.map((permutation) => ({ ...schema, allOf: permutation })); } // No $ref or dependencies or allOf attribute was found, returning the original schema. return [schema]; @@ -296,7 +293,7 @@ export function resolveAllReferences( }; } - return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema; + return isEqual(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 eaeb8dd5f7..04fb79eaac 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 isEqual from 'lodash/isEqual'; 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) => deepEquals(item, _schema)); + const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(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 a33ee03c7a..e0b2abb368 100644 --- a/packages/utils/src/schema/toPathSchema.ts +++ b/packages/utils/src/schema/toPathSchema.ts @@ -1,10 +1,11 @@ import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; import set from 'lodash/set'; import { - ADDITIONAL_PROPERTIES_KEY, ALL_OF_KEY, ANY_OF_KEY, + ADDITIONAL_PROPERTIES_KEY, DEPENDENCIES_KEY, ITEMS_KEY, NAME_KEY, @@ -17,7 +18,6 @@ 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) => deepEquals(item, _schema)); + const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema)); if (sameSchemaIndex === -1) { return toPathSchemaInternal( validator, diff --git a/packages/utils/test/deepEquals.test.ts b/packages/utils/test/deepEquals.test.ts index cc7ea67648..ba8eef53cc 100644 --- a/packages/utils/test/deepEquals.test.ts +++ b/packages/utils/test/deepEquals.test.ts @@ -1,7 +1,7 @@ import { deepEquals } from '../src'; describe('deepEquals()', () => { - // Note: deepEquals implementation uses fast-equal.createCustomEqual, so we focus on the behavioral differences we introduced. + // Note: deepEquals implementation uses isEqualWith, so we focus on the behavioral differences we introduced. it('should assume functions are always equivalent', () => { expect( deepEquals( diff --git a/packages/validator-ajv8/src/precompiledValidator.ts b/packages/validator-ajv8/src/precompiledValidator.ts index 648d52d274..3b201b9a90 100644 --- a/packages/validator-ajv8/src/precompiledValidator.ts +++ b/packages/validator-ajv8/src/precompiledValidator.ts @@ -1,21 +1,21 @@ import { ErrorObject } from 'ajv'; import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; import { CustomValidator, - deepEquals, ErrorSchema, ErrorTransformer, FormContextType, hashForSchema, ID_KEY, JUNK_OPTION_ID, - retrieveSchema, RJSFSchema, StrictRJSFSchema, toErrorList, UiSchema, ValidationData, ValidatorType, + retrieveSchema, } from '@rjsf/utils'; import { CompiledValidateFunction, Localizer, ValidatorFunctions } from './types'; @@ -92,10 +92,10 @@ export default class AJV8PrecompiledValidator< * @param [formData] - The form data to validate if any */ ensureSameRootSchema(schema: S, formData?: T) { - if (!deepEquals(schema, this.rootSchema)) { + if (!isEqual(schema, this.rootSchema)) { // Resolve the root schema with the passed in form data since that may affect the resolution const resolvedRootSchema = retrieveSchema(this, this.rootSchema, this.rootSchema, formData); - if (!deepEquals(schema, resolvedRootSchema)) { + if (!isEqual(schema, resolvedRootSchema)) { throw new Error( 'The schema associated with the precompiled validator differs from the rootSchema provided for validation' );