anyOf elements type doesn't update

here’s my schema

export const Schema = {
  "title": "SQLDatasource",
  "description": "--Public API--",
  "type": "object",
  "properties": {
    "type": {
      "title": "Type",
      "default": "sql",
      "enum": ["sql"],
      "type": "string"
    },
    "name": {
      "title": "Name",
      "type": "string"
    },
    "assets": {
      "title": "Assets",
      "default": [],
      "type": "array",
      "items": {
        "anyOf": [
          {
            "$ref": "#/definitions/TableAsset"
          },
          {
            "$ref": "#/definitions/QueryAsset"
          }
        ]
      }
    },
  },
  "required": ["name"],
  "additionalProperties": false,
  "definitions": {
    "TableAsset": {
      "title": "TableAsset",
      "description": "A _SQLAsset Mixin\n\nThis is used as a mixin for _SQLAsset subclasses to give them the TableAsset functionality\nthat can be used by different SQL datasource subclasses.\n\nFor example see TableAsset defined in this module and SqliteTableAsset defined in\nsqlite_datasource.py",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "type": {
          "title": "Type",
          "default": "table",
          "enum": ["table"],
          "type": "string"
        },
        "table_name": {
          "title": "Table Name",
          "description": "Name of the SQL table. Will default to the value of `name` if not provided.",
          "default": "",
          "type": "string"
        },
        "schema_name": {
          "title": "Schema Name",
          "type": "string"
        }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "QueryAsset": {
      "title": "QueryAsset",
      "description": "Base model for most fluent datasource related pydantic models.\n\nAdds yaml dumping and parsing methods.\n\nExtra fields are not allowed.\n\nSerialization methods default to `exclude_unset = True` to prevent serializing\nconfigs full of mostly unset default values.\nAlso prevents passing along unset kwargs to BatchSpec.\nhttps://docs.pydantic.dev/usage/exporting_models/",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "type": {
          "title": "Type",
          "default": "query",
          "enum": ["query"],
          "type": "string"
        },
        "query": {
          "title": "Query",
          "type": "string"
        }
      },
      "required": ["name", "query"],
      "additionalProperties": false
    }
  }
} as const

and here’s my UISchenas for tableAsset

import { JsonFormsUISchemaRegistryEntry, JsonSchema, RuleEffect } from "@jsonforms/core"
import { UISchema } from "./ui-schema"

export const tableAssetUISchema: UISchema = {
  type: "VerticalLayout",
  elements: [
    {
      type: "Control",
      scope: "#/properties/type",
      rule: {
        effect: RuleEffect.HIDE,
        condition: {},
      },
    },
    {
      type: "Control",
      scope: "#/properties/name",
      label: "Asset name",
    },
    {
      type: "Control",
      scope: "#/properties/table_name",
    },
  ],
}

The problem is that inside data whatever option I select (table asset or query asset) it always sets type to Table

here’s my custom anyOfRenderer

import { useCallback, useState } from "react"
import { Radio, RadioChangeEvent } from "antd"
import {
  CombinatorSubSchemaRenderInfo,
  createCombinatorRenderInfos,
  isAnyOfControl,
  JsonFormsRendererRegistryEntry,
  JsonSchema,
  rankWith,
  StatePropsOfCombinator,
} from "@jsonforms/core"
import { JsonFormsDispatch, withJsonFormsAnyOfProps } from "@jsonforms/react"
import styled from "styled-components"

import { CombinatorProperties } from "./CombinatorProperties"
import { startCase } from "lodash-es"

export const AnyOfRendererRegistryEntry: JsonFormsRendererRegistryEntry = {
  tester: rankWith(3, isAnyOfControl),
  renderer: withJsonFormsAnyOfProps(AnyOfRenderer),
}

const StyledRadioGroup = styled(Radio.Group)`
  margin-bottom: ${({ theme }) => theme.spacing.vertical.s};
`

export function AnyOfRenderer({
  cells,
  indexOfFittingSchema,
  path,
  renderers,
  rootSchema,
  schema,
  uischema,
  uischemas,
  visible,
}: StatePropsOfCombinator) {
  const [selectedAnyOf, setSelectedAnyOf] = useState<number>(indexOfFittingSchema || 0)
  const handleChange = useCallback((e: RadioChangeEvent) => setSelectedAnyOf(e.target.value), [setSelectedAnyOf])
  const anyOf = "anyOf"
  const anyOfRenderInfos = createCombinatorRenderInfos(
    (schema as JsonSchema).anyOf as JsonSchema[],
    rootSchema,
    anyOf,
    uischema,
    path,
    uischemas,
  )

  const computeLabel = (anyOfRenderInfo: CombinatorSubSchemaRenderInfo) => {
    const label = anyOfRenderInfo.label.startsWith("anyOf") ? anyOfRenderInfo.schema.title : anyOfRenderInfo.label
    return startCase(label)
  }

  return (
    <div hidden={!visible}>
      <CombinatorProperties schema={schema} combinatorKeyword={anyOf} path={path} />
      <StyledRadioGroup
        options={anyOfRenderInfos.map((anyOfRenderInfo, index) => ({
          // revert this when this is merged & released: https://github.com/eclipsesource/jsonforms/pull/2165#issuecomment-1640981617
          label: computeLabel(anyOfRenderInfo),
          value: index,
        }))}
        onChange={handleChange}
        value={selectedAnyOf}
      />

      {anyOfRenderInfos.map(
        (anyOfRenderInfo, anyOfIndex) =>
          selectedAnyOf === anyOfIndex && (
            <JsonFormsDispatch
              key={anyOfIndex}
              schema={anyOfRenderInfo.schema}
              uischemas={uischemas}
              uischema={anyOfRenderInfo.uischema}
              path={path}
              renderers={renderers}
              cells={cells}
            />
          ),
      )}
    </div>
  )
}

Another issue is that in case I type in “Table fields” some data then switch to Query type and use that fields I get data from all mixed fields and I have no Idea what type (query or table) was selected in UI

As I see in Material renderer and in mine (as it was mostly copied) change of Tab \ Radio happens outside <JsonFormsDispatch /> component. So is there a way to tell <JsonFormsDispatch /> what tab is selected right now?

Hi @elenajdanova,

The problem with the combinators, i.e. anyOf, allOf, oneOf is that they are hard to support generically. Our combinator renderers are therefore very limited. In the case of anyOf and oneOf they try to determine the fitting subschema by running a limited validation run against each of the sub schemas. However this often fails, for example if the entry is currently not valid against any of them. Not only that but the validation step is also pretty expensive, so if you can skip that then that’s much better.

If I see correctly then you probably handle the default value via AJV? Sadly AJV does not support default for combinators (i.e. anyOf, allOf and oneOf) simply because it’s often not decidable which default value to take.

I think the reason why you always get table injected, is because of our subschema validation runs. We reuse the same AJV which is handed in. So if default generation is turned on, our intermediate validation runs will modify the data in place. This is an oversight on our part.


In most cases, including yours, I would recommend not using the generic combinator utilities already provided by JSON Forms but tailoring your custom renderer exactly to your needs.

In your case the anyOf entries seem to be uniquely identifiable by a type property which both of them model. So I would recommend implementing a custom anyOf/oneOf renderer which:

  • knows that the type property is the one to look at for distinguishing between the different entries
  • therefore handles the type property itself, for example by rendering it first. It collects all valid entries for type by walking through all anyOf entries. For the remaining properties it can dispatch back to JSON Forms.
    • an easy way to accomplish this is to just dispatch the rendering of the whole object to JSON Forms with a small custom type renderer which just renders nothing. Alternatively the type could also be set to visible: false.
  • when the type is modifed by the user the data object is cleared, the type set accordingly and a dispatch with the correct schema back to JSON Forms executed

This will render a far better and more performant UI than just replicating the very generic JSON Forms anyof renderers.

Does this make sense to you? Let me know if you have questions for the implementation.

Follow up question, as I’m trying to put together custom anyOf renderer and set the data I want via
handleChange(path, { name: 't', query: 't', type: "query" })

I still get out of JsonForms data that has empty fields from other type like
{name: "t", query: "t", table_name: "", type: "query"}