From e41c15dead3d38efeb7737cfd8b9151ff4ec8e03 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Tue, 12 Aug 2025 13:35:27 -0400 Subject: [PATCH 1/9] preserve the form data so when we update the additionalErrors the data is not reset to the initial state --- packages/vue-vuetify/dev/views/ExampleView.vue | 1 - packages/vue/src/components/JsonForms.vue | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vue-vuetify/dev/views/ExampleView.vue b/packages/vue-vuetify/dev/views/ExampleView.vue index e4274ab3d1..ff18efe564 100644 --- a/packages/vue-vuetify/dev/views/ExampleView.vue +++ b/packages/vue-vuetify/dev/views/ExampleView.vue @@ -91,7 +91,6 @@ const onChange = (event: JsonFormsChangeEvent): void => { monaco.Uri.parse(toDataUri(props.example.name)), event.data !== undefined ? JSON.stringify(event.data, null, 2) : '', ); - state.data = event.data; } errors.value = event.errors; }; diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index d21863e574..fb17a5e7d7 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -251,6 +251,9 @@ export default defineComponent({ ); }, eventToEmit(newEvent) { + // update the data so if we change the additionalErrors this won't reset the form data + this.dataToUse = newEvent.data; + this.$emit('change', newEvent); }, i18n: { From d5218b15879ab69274c3e81b735737f0d33c04b7 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 23 Aug 2025 18:09:42 -0400 Subject: [PATCH 2/9] also update the schema and uischema when they depend on the data and the data is changed --- packages/vue/src/components/JsonForms.vue | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index fb17a5e7d7..59b791641a 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -212,6 +212,19 @@ export default defineComponent({ }, data(newData) { this.dataToUse = newData; + + if (!this.schema) { + const generatorData = isObject(newData) ? newData : {}; + this.schemaToUse = Generate.jsonSchema(generatorData); + if (!this.uischema) { + this.uischemaToUse = Generate.uiSchema( + this.schemaToUse, + undefined, + undefined, + this.schemaToUse + ); + } + } }, renderers(newRenderers) { this.jsonforms.renderers = newRenderers; From 0b979c0e98396b02a61a1d6585f36bd7e83e56ac Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 23 Aug 2025 19:47:46 -0400 Subject: [PATCH 3/9] properly generate schema in case when the data is not an object but simple type like number, string, array and etc. --- packages/core/src/generators/schema.ts | 2 +- packages/vue/src/components/JsonForms.vue | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/core/src/generators/schema.ts b/packages/core/src/generators/schema.ts index f3b1d2324b..2de7ef0a3c 100644 --- a/packages/core/src/generators/schema.ts +++ b/packages/core/src/generators/schema.ts @@ -177,5 +177,5 @@ export const generateJsonSchema = ( const gen = new Gen(findOption); - return gen.schemaObject(instance); + return gen.property(instance); }; diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index 59b791641a..e506123d9a 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -33,12 +33,6 @@ import DispatchRenderer from './DispatchRenderer.vue'; import type Ajv from 'ajv'; import type { ErrorObject } from 'ajv'; -// TODO fix @typescript-eslint/ban-types -// eslint-disable-next-line @typescript-eslint/ban-types -const isObject = (elem: any): elem is Object => { - return elem && typeof elem === 'object'; -}; - const EMPTY: ErrorObject[] = reactive([]); export default defineComponent({ @@ -124,9 +118,8 @@ export default defineComponent({ emits: ['change'], data() { const dataToUse = this.data; - const generatorData = isObject(dataToUse) ? dataToUse : {}; const schemaToUse: JsonSchema = - this.schema ?? Generate.jsonSchema(generatorData); + this.schema ?? Generate.jsonSchema(dataToUse); const uischemaToUse = this.uischema ?? Generate.uiSchema(schemaToUse, undefined, undefined, schemaToUse); @@ -189,8 +182,7 @@ export default defineComponent({ }, watch: { schema(newSchema) { - const generatorData = isObject(this.data) ? this.data : {}; - this.schemaToUse = newSchema ?? Generate.jsonSchema(generatorData); + this.schemaToUse = newSchema ?? Generate.jsonSchema(this.data); if (!this.uischema) { this.uischemaToUse = Generate.uiSchema( this.schemaToUse, @@ -214,8 +206,7 @@ export default defineComponent({ this.dataToUse = newData; if (!this.schema) { - const generatorData = isObject(newData) ? newData : {}; - this.schemaToUse = Generate.jsonSchema(generatorData); + this.schemaToUse = Generate.jsonSchema(newData); if (!this.uischema) { this.uischemaToUse = Generate.uiSchema( this.schemaToUse, From ebe262b137ba38e83aa61c0f4db148b362a89796 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 23 Aug 2025 23:43:02 -0400 Subject: [PATCH 4/9] always return valid UISchemaElement, in case of empty schema this was returning null --- packages/core/src/generators/uischema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/generators/uischema.ts b/packages/core/src/generators/uischema.ts index 8e5eb252a5..d394ee4276 100644 --- a/packages/core/src/generators/uischema.ts +++ b/packages/core/src/generators/uischema.ts @@ -138,7 +138,7 @@ const generateUISchema = ( const types = deriveTypes(jsonSchema); if (types.length === 0) { - return null; + return createLayout('VerticalLayout'); } if (types.length > 1) { From 51aa0bc1877361fb624dce5bd007eff983b79483 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sun, 24 Aug 2025 00:16:58 -0400 Subject: [PATCH 5/9] revert back the ability to return null from the generate but default to non null uischema in JsonForms --- packages/core/src/generators/uischema.ts | 2 +- packages/vue/src/components/JsonForms.vue | 36 ++++++++--------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/packages/core/src/generators/uischema.ts b/packages/core/src/generators/uischema.ts index d394ee4276..8e5eb252a5 100644 --- a/packages/core/src/generators/uischema.ts +++ b/packages/core/src/generators/uischema.ts @@ -138,7 +138,7 @@ const generateUISchema = ( const types = deriveTypes(jsonSchema); if (types.length === 0) { - return createLayout('VerticalLayout'); + return null; } if (types.length > 1) { diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index e506123d9a..c7fbc16e6a 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -26,6 +26,7 @@ import { defaultMiddleware, Middleware, JsonFormsSubStates, + Layout, } from '@jsonforms/core'; import { JsonFormsChangeEvent, MaybeReadonly } from '../types'; import DispatchRenderer from './DispatchRenderer.vue'; @@ -35,6 +36,14 @@ import type { ErrorObject } from 'ajv'; const EMPTY: ErrorObject[] = reactive([]); +const createDefaultLayout = (): Layout => ({ + type: 'VerticalLayout', + elements: [], +}); +const generateUISchema = (schema: JsonSchema) => + Generate.uiSchema(schema, undefined, undefined, schema) ?? + createDefaultLayout(); + export default defineComponent({ name: 'JsonForms', components: { @@ -120,9 +129,7 @@ export default defineComponent({ const dataToUse = this.data; const schemaToUse: JsonSchema = this.schema ?? Generate.jsonSchema(dataToUse); - const uischemaToUse = - this.uischema ?? - Generate.uiSchema(schemaToUse, undefined, undefined, schemaToUse); + const uischemaToUse = this.uischema ?? generateUISchema(schemaToUse); const initCore = (): JsonFormsCore => { const initialCore = { data: dataToUse, @@ -184,23 +191,11 @@ export default defineComponent({ schema(newSchema) { this.schemaToUse = newSchema ?? Generate.jsonSchema(this.data); if (!this.uischema) { - this.uischemaToUse = Generate.uiSchema( - this.schemaToUse, - undefined, - undefined, - this.schemaToUse - ); + this.uischemaToUse = generateUISchema(this.schemaToUse); } }, uischema(newUischema) { - this.uischemaToUse = - newUischema ?? - Generate.uiSchema( - this.schemaToUse, - undefined, - undefined, - this.schemaToUse - ); + this.uischemaToUse = newUischema ?? generateUISchema(this.schemaToUse); }, data(newData) { this.dataToUse = newData; @@ -208,12 +203,7 @@ export default defineComponent({ if (!this.schema) { this.schemaToUse = Generate.jsonSchema(newData); if (!this.uischema) { - this.uischemaToUse = Generate.uiSchema( - this.schemaToUse, - undefined, - undefined, - this.schemaToUse - ); + this.uischemaToUse = generateUISchema(this.schemaToUse); } } }, From b27ce7ff7b98bbc8398b1799d2c95cf46797649d Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Fri, 5 Sep 2025 13:26:56 -0400 Subject: [PATCH 6/9] only for undefined use emtpy data for others the core can handle those --- packages/vue/src/components/JsonForms.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index c7fbc16e6a..cb0781bb8f 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -126,7 +126,7 @@ export default defineComponent({ }, emits: ['change'], data() { - const dataToUse = this.data; + const dataToUse = this.data === undefined ? {} : this.data; const schemaToUse: JsonSchema = this.schema ?? Generate.jsonSchema(dataToUse); const uischemaToUse = this.uischema ?? generateUISchema(schemaToUse); @@ -189,7 +189,7 @@ export default defineComponent({ }, watch: { schema(newSchema) { - this.schemaToUse = newSchema ?? Generate.jsonSchema(this.data); + this.schemaToUse = newSchema ?? Generate.jsonSchema(this.dataToUse); if (!this.uischema) { this.uischemaToUse = generateUISchema(this.schemaToUse); } @@ -198,10 +198,10 @@ export default defineComponent({ this.uischemaToUse = newUischema ?? generateUISchema(this.schemaToUse); }, data(newData) { - this.dataToUse = newData; + this.dataToUse = newData === undefined ? {} : newData; if (!this.schema) { - this.schemaToUse = Generate.jsonSchema(newData); + this.schemaToUse = Generate.jsonSchema(this.dataToUse); if (!this.uischema) { this.uischemaToUse = generateUISchema(this.schemaToUse); } From aa5b3c48450d4b692b48aad08108e838df201200 Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 18 Apr 2026 20:31:57 -0400 Subject: [PATCH 7/9] add test case for schema generation for primitive types --- packages/core/test/generators/schema.test.ts | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/core/test/generators/schema.test.ts b/packages/core/test/generators/schema.test.ts index 679defefa3..b6aa80ceda 100644 --- a/packages/core/test/generators/schema.test.ts +++ b/packages/core/test/generators/schema.test.ts @@ -26,6 +26,37 @@ import test from 'ava'; import { generateJsonSchema } from '../../src/generators/schema'; +test('default schema generation root primitive types', (t) => { + t.deepEqual(generateJsonSchema('hello' as any), { + type: 'string', + }); + t.deepEqual(generateJsonSchema(42 as any), { + type: 'integer', + }); + t.deepEqual(generateJsonSchema(3.14 as any), { + type: 'number', + }); + t.deepEqual(generateJsonSchema(true as any), { + type: 'boolean', + }); + t.deepEqual(generateJsonSchema(null as any), { + type: 'null', + }); +}); + +test('default schema generation root array types', (t) => { + t.deepEqual(generateJsonSchema([] as any), { + type: 'array', + items: {}, + }); + t.deepEqual(generateJsonSchema([1, 2] as any), { + type: 'array', + items: { + type: 'integer', + }, + }); +}); + test('default schema generation basic types', (t) => { const instance: any = { boolean: false, From 4af78849a360534788dc5818678729deca84b9ce Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 18 Apr 2026 21:12:43 -0400 Subject: [PATCH 8/9] preserve undefined Vue form data and type primitive schema generation --- packages/core/src/generators/Generate.ts | 4 +- packages/core/src/generators/schema.ts | 10 +-- packages/core/test/generators/schema.test.ts | 14 ++-- packages/vue/src/components/JsonForms.vue | 15 +++-- packages/vue/tests/unit/JsonForms.spec.ts | 67 +++++++++++++++++++- 5 files changed, 87 insertions(+), 23 deletions(-) diff --git a/packages/core/src/generators/Generate.ts b/packages/core/src/generators/Generate.ts index ad1b17354d..4a8d9adebe 100644 --- a/packages/core/src/generators/Generate.ts +++ b/packages/core/src/generators/Generate.ts @@ -28,9 +28,7 @@ import { generateJsonSchema } from './schema'; import { createControlElement, generateDefaultUISchema } from './uischema'; export const Generate: { - // TODO fix @typescript-eslint/ban-types - // eslint-disable-next-line @typescript-eslint/ban-types - jsonSchema(instance: Object, options?: any): JsonSchema; + jsonSchema(instance: unknown, options?: any): JsonSchema; uiSchema( jsonSchema: JsonSchema, layoutType?: string, diff --git a/packages/core/src/generators/schema.ts b/packages/core/src/generators/schema.ts index 2de7ef0a3c..623966e97f 100644 --- a/packages/core/src/generators/schema.ts +++ b/packages/core/src/generators/schema.ts @@ -52,9 +52,7 @@ class Gen { private findOption: (props: Properties) => (optionName: string) => any ) {} - // TODO fix @typescript-eslint/ban-types - // eslint-disable-next-line @typescript-eslint/ban-types - schemaObject = (data: Object): JsonSchema4 => { + schemaObject = (data: Record): JsonSchema4 => { const props: Properties = this.properties(data); const schema: JsonSchema4 = { type: 'object', @@ -140,14 +138,12 @@ class Gen { /** * Generate a JSON schema based on the given data and any additional options. - * @param {Object} instance the data to create a JSON schema for + * @param {unknown} instance the data to create a JSON schema for * @param {any} options any additional options that may alter the generated JSON schema * @returns {JsonSchema} the generated schema */ export const generateJsonSchema = ( - // TODO fix @typescript-eslint/ban-types - // eslint-disable-next-line @typescript-eslint/ban-types - instance: Object, + instance: unknown, options: any = {} ): JsonSchema4 => { const findOption = diff --git a/packages/core/test/generators/schema.test.ts b/packages/core/test/generators/schema.test.ts index b6aa80ceda..634375b929 100644 --- a/packages/core/test/generators/schema.test.ts +++ b/packages/core/test/generators/schema.test.ts @@ -27,29 +27,29 @@ import test from 'ava'; import { generateJsonSchema } from '../../src/generators/schema'; test('default schema generation root primitive types', (t) => { - t.deepEqual(generateJsonSchema('hello' as any), { + t.deepEqual(generateJsonSchema('hello'), { type: 'string', }); - t.deepEqual(generateJsonSchema(42 as any), { + t.deepEqual(generateJsonSchema(42), { type: 'integer', }); - t.deepEqual(generateJsonSchema(3.14 as any), { + t.deepEqual(generateJsonSchema(3.14), { type: 'number', }); - t.deepEqual(generateJsonSchema(true as any), { + t.deepEqual(generateJsonSchema(true), { type: 'boolean', }); - t.deepEqual(generateJsonSchema(null as any), { + t.deepEqual(generateJsonSchema(null), { type: 'null', }); }); test('default schema generation root array types', (t) => { - t.deepEqual(generateJsonSchema([] as any), { + t.deepEqual(generateJsonSchema([]), { type: 'array', items: {}, }); - t.deepEqual(generateJsonSchema([1, 2] as any), { + t.deepEqual(generateJsonSchema([1, 2]), { type: 'array', items: { type: 'integer', diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index cb0781bb8f..a3541ae3bd 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -40,6 +40,8 @@ const createDefaultLayout = (): Layout => ({ type: 'VerticalLayout', elements: [], }); +const getSchemaGeneratorInput = (data: any) => + data === undefined ? {} : data; const generateUISchema = (schema: JsonSchema) => Generate.uiSchema(schema, undefined, undefined, schema) ?? createDefaultLayout(); @@ -126,9 +128,9 @@ export default defineComponent({ }, emits: ['change'], data() { - const dataToUse = this.data === undefined ? {} : this.data; + const dataToUse = this.data; const schemaToUse: JsonSchema = - this.schema ?? Generate.jsonSchema(dataToUse); + this.schema ?? Generate.jsonSchema(getSchemaGeneratorInput(dataToUse)); const uischemaToUse = this.uischema ?? generateUISchema(schemaToUse); const initCore = (): JsonFormsCore => { const initialCore = { @@ -189,7 +191,8 @@ export default defineComponent({ }, watch: { schema(newSchema) { - this.schemaToUse = newSchema ?? Generate.jsonSchema(this.dataToUse); + this.schemaToUse = + newSchema ?? Generate.jsonSchema(getSchemaGeneratorInput(this.dataToUse)); if (!this.uischema) { this.uischemaToUse = generateUISchema(this.schemaToUse); } @@ -198,10 +201,12 @@ export default defineComponent({ this.uischemaToUse = newUischema ?? generateUISchema(this.schemaToUse); }, data(newData) { - this.dataToUse = newData === undefined ? {} : newData; + this.dataToUse = newData; if (!this.schema) { - this.schemaToUse = Generate.jsonSchema(this.dataToUse); + this.schemaToUse = Generate.jsonSchema( + getSchemaGeneratorInput(this.dataToUse) + ); if (!this.uischema) { this.uischemaToUse = generateUISchema(this.schemaToUse); } diff --git a/packages/vue/tests/unit/JsonForms.spec.ts b/packages/vue/tests/unit/JsonForms.spec.ts index b39e505273..e1edbb89fa 100644 --- a/packages/vue/tests/unit/JsonForms.spec.ts +++ b/packages/vue/tests/unit/JsonForms.spec.ts @@ -1,5 +1,10 @@ -import { JsonFormsUISchemaRegistryEntry, Generate } from '@jsonforms/core'; +import { + Actions, + JsonFormsUISchemaRegistryEntry, + Generate, +} from '@jsonforms/core'; import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import { JsonForms } from '../../src'; import { bindings } from '../testHelper'; @@ -30,6 +35,20 @@ describe('JsonForms.vue', () => { ); }); + it('generates schema for primitive root data', () => { + const data = 5.5; + const renderers: JsonFormsUISchemaRegistryEntry[] = []; + const wrapper = shallowMount( + JsonForms, + bindings({ + props: { data, renderers }, + }) + ); + expect((wrapper.vm as any).jsonforms.core.schema).toEqual( + Generate.jsonSchema(data) + ); + }); + it('generates ui schema when not given via prop', () => { const data = { number: 5.5 }; const schema = { @@ -47,4 +66,50 @@ describe('JsonForms.vue', () => { Generate.uiSchema(schema) ); }); + + it('preserves undefined root data when the data prop is cleared', async () => { + const data = { number: 5.5 }; + const renderers: JsonFormsUISchemaRegistryEntry[] = []; + const wrapper = shallowMount( + JsonForms, + bindings({ + props: { data, renderers }, + }) + ); + + await wrapper.setProps({ data: undefined }); + + expect((wrapper.vm as any).jsonforms.core.data).toBeUndefined(); + + const changeEvents = wrapper.emitted('change'); + const latestChangeEvent: any = changeEvents?.[changeEvents.length - 1]?.[0]; + expect(latestChangeEvent?.data).toBeUndefined(); + }); + + it('preserves edited data when additionalErrors change', async () => { + const data = { number: 5.5 }; + const renderers: JsonFormsUISchemaRegistryEntry[] = []; + const wrapper = shallowMount( + JsonForms, + bindings({ + props: { data, renderers }, + }) + ); + + (wrapper.vm as any).dispatch(Actions.update('number', () => 6)); + await nextTick(); + + await wrapper.setProps({ + additionalErrors: [ + { + instancePath: '/number', + schemaPath: '#/properties/number/minimum', + keyword: 'minimum', + params: { comparison: '>=', limit: 7 }, + }, + ], + }); + + expect((wrapper.vm as any).jsonforms.core.data).toEqual({ number: 6 }); + }); }); From 66e5c00c226abdf43742e12e60261b5aa3c5de4a Mon Sep 17 00:00:00 2001 From: Krasimir Chobantonov Date: Sat, 18 Apr 2026 21:59:53 -0400 Subject: [PATCH 9/9] avoid regenerating Vue schemas when parent echoes unchanged form shape --- packages/vue/src/components/JsonForms.vue | 13 +++-- packages/vue/tests/unit/JsonForms.spec.ts | 66 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/packages/vue/src/components/JsonForms.vue b/packages/vue/src/components/JsonForms.vue index a3541ae3bd..97b1071805 100644 --- a/packages/vue/src/components/JsonForms.vue +++ b/packages/vue/src/components/JsonForms.vue @@ -8,6 +8,7 @@