Skip to content

Commit e0938e3

Browse files
gigibiffi84claude
andcommitted
Merge the reworked Form Validator into the fork's integration line
Brings feat/form-validator-bridge-v2 (the eclipsesource#2644 rework on current upstream master) into fork master and ports the stacked work to the new API: - core: `state.validator` holds the Form Validator, the custom factory is kept in `state.validatorFactory`; no default Ajv instance is created while a factory is set (eclipsesource#2648). - rules (eclipsesource#2645): conditions are evaluated with the validator factory (one cached Form Validator per condition schema) or the Ajv instance; `matches` and the bound-validator branch are gone. `getRuleValidator` reads `validatorFactory ?? ajv`. - bindings (eclipsesource#2646): the `validator` prop of React, Vue and Angular is a FormValidatorFactory; `useValidator` reads `state.validator`. - standard-schema: `fromStandardSchema` returns a factory. For its own JSON Schema it validates with the library and sets `parentSchema` per issue (`schemaAt`, moved here from core); for other schemas, e.g. rule conditions, it uses core's schema matcher. No `severity`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B36yeVG6EUUhwMCEzybGEH
2 parents 9c0794a + aee10d0 commit e0938e3

64 files changed

Lines changed: 3552 additions & 3707 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎MIGRATION.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
## Migration to JSON Forms 3.9
44

5+
### `validator` in the core state is a Form Validator
6+
7+
JSON Forms 3.9 adds a `validator` option to plug in a custom validation library. It takes a factory `(schema) => FormValidator` and takes precedence over `ajv`. Without it, Ajv validates as before.
8+
9+
As a consequence, `JsonFormsCore.validator` now holds a `FormValidator` instead of the compiled Ajv `ValidateFunction`.
10+
If you read `state.validator` or build the core state by hand, call `validator.validate(data)`, which returns `ValidationIssue`s, or wrap a compiled Ajv function with `compiledAjvValidator(validateFn)`.
11+
12+
### JSON Schema generation supports non-object root values
13+
14+
`generateJsonSchema` now generates a schema matching the root value's actual type. In particular, arrays produce an array schema with an `items` schema, primitives produce their corresponding primitive schema, and `undefined` produces an empty schema.
15+
16+
Previously all root values were handled as objects. For example, `generateJsonSchema([1, 2])` produced an object schema with properties named `"0"` and `"1"`; it now produces an array schema with an integer `items` schema. If you depend on the previous object-shaped output, adapt the generated schema before consuming it.
17+
18+
### Default UI schema generation always returns a layout
19+
20+
`generateDefaultUISchema` previously returned `null` when no UI schema elements could be generated. It now returns an empty layout of the requested type instead.
21+
22+
If you call `generateDefaultUISchema` directly, remove any `null` handling and check the returned layout's `elements` when you need to know whether controls were generated.
23+
524
### Data update paths treat all segments literally
625

726
Data updates (e.g. dispatched `update` actions) previously wrote to the form data via lodash's `set`/`unset`, which interpret bracket notation and array indices in paths.

‎package.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
"build:examples-app": "lerna run build:examples-app && node packages/examples-app/prepare-examples-app.js"
2222
},
2323
"devDependencies": {
24-
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.5",
25-
"@babel/plugin-proposal-optional-chaining": "^7.16.5",
24+
"@babel/preset-env": "^7.29.7",
2625
"@istanbuljs/nyc-config-typescript": "^1.0.2",
2726
"@types/lodash": "^4.14.149",
2827
"ajv": "^8.18.0",

‎packages/angular-material/package.json‎

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"rxjs": "^6.6.0 || ^7.4.0"
7979
},
8080
"dependencies": {
81-
"lodash": "^4.17.21"
81+
"lodash": "^4.18.1"
8282
},
8383
"devDependencies": {
8484
"@angular-devkit/build-angular": "^20.0.0",
@@ -87,20 +87,18 @@
8787
"@angular-eslint/eslint-plugin-template": "^20.7.0",
8888
"@angular-eslint/schematics": "^20.7.0",
8989
"@angular-eslint/template-parser": "^20.7.0",
90-
"@angular/animations": "^20.3.24",
90+
"@angular/animations": "^20.3.33",
9191
"@angular/cdk": "^20.2.14",
92-
"@angular/cli": "^20.3.27",
93-
"@angular/common": "^20.3.24",
94-
"@angular/compiler": "^20.3.24",
95-
"@angular/compiler-cli": "^20.3.24",
96-
"@angular/core": "^20.3.24",
97-
"@angular/forms": "^20.3.24",
92+
"@angular/cli": "^20.3.38",
93+
"@angular/common": "^20.3.33",
94+
"@angular/compiler": "^20.3.33",
95+
"@angular/compiler-cli": "^20.3.33",
96+
"@angular/core": "^20.3.33",
97+
"@angular/forms": "^20.3.33",
9898
"@angular/material": "^20.2.14",
99-
"@angular/platform-browser": "^20.3.24",
100-
"@angular/platform-browser-dynamic": "^20.3.24",
101-
"@angular/router": "^20.3.24",
102-
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.5",
103-
"@babel/plugin-proposal-optional-chaining": "^7.16.5",
99+
"@angular/platform-browser": "^20.3.33",
100+
"@angular/platform-browser-dynamic": "^20.3.33",
101+
"@angular/router": "^20.3.33",
104102
"@jsonforms/angular": "workspace:*",
105103
"@jsonforms/core": "workspace:*",
106104
"@jsonforms/examples": "workspace:*",
@@ -110,9 +108,8 @@
110108
"@types/node": "^24.12.2",
111109
"@typescript-eslint/eslint-plugin": "^5.54.1",
112110
"@typescript-eslint/parser": "^5.54.1",
113-
"babel-loader": "^8.0.6",
114111
"copy-webpack-plugin": "^11.0.0",
115-
"dayjs": "^1.11.10",
112+
"dayjs": "^1.11.11",
116113
"eslint": "^8.56.0",
117114
"eslint-config-prettier": "^8.7.0",
118115
"eslint-plugin-import": "^2.27.5",
@@ -134,7 +131,7 @@
134131
"prettier": "^2.8.4",
135132
"protractor": "^7.0.0",
136133
"rimraf": "^6.1.0",
137-
"rxjs": "^6.6.0",
134+
"rxjs": "^6.6.7",
138135
"ts-loader": "^9.5.1",
139136
"tslib": "^2.5.0",
140137
"typedoc": "~0.25.3",

‎packages/angular/package.json‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
]
6969
},
7070
"dependencies": {
71-
"lodash": "^4.17.21"
71+
"lodash": "^4.18.1"
7272
},
7373
"peerDependencies": {
7474
"@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
@@ -81,10 +81,10 @@
8181
"@angular-eslint/eslint-plugin-template": "^20.7.0",
8282
"@angular-eslint/schematics": "^20.7.0",
8383
"@angular-eslint/template-parser": "^20.7.0",
84-
"@angular/compiler": "^20.3.24",
85-
"@angular/compiler-cli": "^20.3.24",
86-
"@angular/core": "^20.3.24",
87-
"@angular/forms": "^20.3.24",
84+
"@angular/compiler": "^20.3.33",
85+
"@angular/compiler-cli": "^20.3.33",
86+
"@angular/core": "^20.3.33",
87+
"@angular/forms": "^20.3.33",
8888
"@jsonforms/core": "workspace:*",
8989
"@typescript-eslint/eslint-plugin": "^5.54.1",
9090
"@typescript-eslint/parser": "^5.54.1",
@@ -98,7 +98,7 @@
9898
"nyc": "^15.1.0",
9999
"prettier": "^2.8.4",
100100
"rimraf": "^6.1.0",
101-
"rxjs": "^6.6.0",
101+
"rxjs": "^6.6.7",
102102
"source-map-support": "^0.5.21",
103103
"tslib": "^2.3.0",
104104
"typedoc": "~0.25.3",

‎packages/angular/src/library/jsonforms-root.component.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
UISchemaElement,
4545
UISchemaTester,
4646
ValidationMode,
47-
ValidatorOption,
47+
FormValidatorFactory,
4848
} from '@jsonforms/core';
4949
import type Ajv from 'ajv';
5050
import type { ErrorObject } from 'ajv';
@@ -72,7 +72,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
7272
@Input() readonly: boolean;
7373
@Input() validationMode: ValidationMode;
7474
@Input() ajv: Ajv;
75-
@Input() validator: ValidatorOption;
75+
@Input() validator: FormValidatorFactory;
7676
@Input() config: JsonFormsAngularConfig;
7777
@Input() i18n: JsonFormsI18nState;
7878
@Input() additionalErrors: ErrorObject[];
@@ -97,7 +97,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
9797
uischema: this.uischema,
9898
schema: this.schema,
9999
ajv: this.ajv,
100-
validatorOption: this.validator,
100+
validatorFactory: this.validator,
101101
validationMode: this.validationMode,
102102
additionalErrors: this.additionalErrors,
103103
},

‎packages/angular/src/library/jsonforms.service.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
updateI18n,
4747
Middleware,
4848
defaultMiddleware,
49-
ValidatorOption,
49+
FormValidatorFactory,
5050
} from '@jsonforms/core';
5151
import { BehaviorSubject, Observable } from 'rxjs';
5252
import type { JsonFormsBaseRenderer } from './base.renderer';
@@ -295,7 +295,7 @@ export class JsonFormsAngularService {
295295
ajv: Ajv | typeof USE_STATE_VALUE,
296296
validationMode: ValidationMode | typeof USE_STATE_VALUE,
297297
additionalErrors: ErrorObject[] | typeof USE_STATE_VALUE,
298-
validator: ValidatorOption | typeof USE_STATE_VALUE = USE_STATE_VALUE
298+
validator: FormValidatorFactory | typeof USE_STATE_VALUE = USE_STATE_VALUE
299299
): void {
300300
const newData = data === USE_STATE_VALUE ? this._state.core.data : data;
301301
const newSchema =
@@ -317,7 +317,7 @@ export class JsonFormsAngularService {
317317
: additionalErrors;
318318
const newValidator =
319319
validator === USE_STATE_VALUE
320-
? this._state.core.validatorOption
320+
? this._state.core.validatorFactory
321321
: validator;
322322
this.updateCore(
323323
Actions.updateCore(newData, newSchema, newUischema, {

‎packages/core/package.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@
6868
]
6969
},
7070
"dependencies": {
71-
"@types/json-schema": "^7.0.3",
71+
"@types/json-schema": "^7.0.15",
7272
"ajv": "^8.18.0",
73-
"ajv-formats": "^2.1.0",
74-
"lodash": "^4.17.21"
73+
"ajv-formats": "^2.1.1",
74+
"lodash": "^4.18.1"
7575
},
7676
"devDependencies": {
7777
"@istanbuljs/nyc-config-typescript": "^1.0.2",

‎packages/core/src/actions/actions.ts‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import type AJV from 'ajv';
2727
import type { ErrorObject } from 'ajv';
28-
import type { ValidatorOption } from '../util/formValidator';
28+
import type { FormValidatorFactory } from '../util/formValidator';
2929
import { JsonSchema, UISchemaElement } from '../models';
3030
import { generateDefaultUISchema, generateJsonSchema } from '../generators';
3131

@@ -146,15 +146,9 @@ export interface UpdateCoreAction {
146146
}
147147

148148
export interface InitActionOptions {
149-
/** The AJV instance to validate with. Ignored for form validation when `validator` is set. */
150149
ajv?: AJV;
151-
/**
152-
* A custom Form Validator: a factory producing one per schema, or a
153-
* validator already bound to the schema. Takes precedence over `ajv`.
154-
* Passing `undefined` explicitly switches back to AJV; leaving the key out
155-
* keeps the validator currently in use.
156-
*/
157-
validator?: ValidatorOption;
150+
/** Creates the Form Validator for the schema. Takes precedence over `ajv`. */
151+
validator?: FormValidatorFactory;
158152
validationMode?: ValidationMode;
159153
additionalErrors?: ErrorObject[];
160154
}

‎packages/core/src/generators/Generate.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ import { generateJsonSchema } from './schema';
2828
import { createControlElement, generateDefaultUISchema } from './uischema';
2929

3030
export const Generate: {
31-
// TODO fix @typescript-eslint/ban-types
32-
// eslint-disable-next-line @typescript-eslint/ban-types
33-
jsonSchema(instance: Object, options?: any): JsonSchema;
31+
jsonSchema(instance: unknown, options?: any): JsonSchema;
3432
uiSchema(
3533
jsonSchema: JsonSchema,
3634
layoutType?: string,

‎packages/core/src/generators/schema.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ class Gen {
5252
private findOption: (props: Properties) => (optionName: string) => any
5353
) {}
5454

55-
// TODO fix @typescript-eslint/ban-types
56-
// eslint-disable-next-line @typescript-eslint/ban-types
57-
schemaObject = (data: Object): JsonSchema4 => {
55+
schemaObject = (data: Record<string, any>): JsonSchema4 => {
5856
const props: Properties = this.properties(data);
5957
const schema: JsonSchema4 = {
6058
type: 'object',
@@ -140,14 +138,12 @@ class Gen {
140138

141139
/**
142140
* Generate a JSON schema based on the given data and any additional options.
143-
* @param {Object} instance the data to create a JSON schema for
141+
* @param {unknown} instance the data to create a JSON schema for
144142
* @param {any} options any additional options that may alter the generated JSON schema
145143
* @returns {JsonSchema} the generated schema
146144
*/
147145
export const generateJsonSchema = (
148-
// TODO fix @typescript-eslint/ban-types
149-
// eslint-disable-next-line @typescript-eslint/ban-types
150-
instance: Object,
146+
instance: unknown,
151147
options: any = {}
152148
): JsonSchema4 => {
153149
const findOption =
@@ -177,5 +173,5 @@ export const generateJsonSchema = (
177173

178174
const gen = new Gen(findOption);
179175

180-
return gen.schemaObject(instance);
176+
return gen.property(instance);
181177
};

0 commit comments

Comments
 (0)