Altering layout/display of a single schema param

I have a use case where we have a large JSON schema containing many parameters. For one of these parameters I want to alter its layout using a UI schema but I don’t want to have to add all the other params to the UI schema–just display them by default. So far I’m finding that if you don’t add all the schema params to the UI schema then they simply aren’t rendered on the UI. Is there a way to change the UI layout of a single param using a UI schema without having to specify all the other params as well?

Hi @DiskCrasher,
there is no way to only provide a partial UI Schema to only override the control for one property.
What you could try is invoking JSON Forms default UI Schema generation, find the control for your property (i.e. by its scope) and then adapt this control programmatically.

You can do the generation like this:

import { Generate } from '@jsonforms/core';

const yourJsonSchema = ...; // get your schema from somewhere
const uiSchema = Generate.uiSchema(yourJsonSchema);

Hope that helps,
Lucas

Alternatively, if that’s a use case for you, you could use custom renderers to achieve this.

For example you could implement a custom object renderer which checks the UI Schema for Controls of its properties and then at runtime renders the missing properties too.

1 Like

I’m playing around with generating the UI schema by calling generateDefaultUISchema. My JSON schema contains $ref elements pointing to other schema files which in turn have their own $ref elements. I use json-schema-ref-parser to combine them all into a single JSON schema. However, when passing this de-referenced schema to the function above, the UI schema that gets returned only contains the top-level schema params and none of the referenced schema params. It’s as if it “re-referenced” the $ref tags.

Furthermore, if I specify the returned UI schema object in the JSONForms uischema parameter I get an error: “No applicable renderer found.”

Hi @DiskCrasher,

Our uischema generator always generates “shallow” UI schemas. When a control points to an object or array, then the respective off-the-shelf renderer will again trigger the ui schema generator for it’s subschema and so on.

This means that either our generator has a bug or one of the subschemas pointed to by a generated Control is not supported by our off the shelf renderers.

So is there no way to auto-generate a complete UI schema that includes all the referenced schemas? And what does the undocumented rootSchema parameter refer to in generateDefaultUISchema?

All the subschemas render fine in JSON Forms when no UI schema is specified, so perhaps there is a bug.

@sdirix Forgot to tag you.

Hi @DiskCrasher,

The UI Schema generator is very simple and just looks at the JSON Schema at hand. It doesn’t care how it was created and how many references are in it. It will always just create a vertical layout with all properties as Controls.

If you like a different behavior you can copy the code and adjust it to your liking.

Within JSON Forms we often only hand down sub-schemas of the overall schema into the respective renderers. However they can contain references pointing to a different part of the schema and need to be resolved from the root of the schema. This is why we also hand over the whole schema (= rootSchema) to the resolve function.

Can you post the full JSON Schema which does not work for you? Otherwise it’s hard to diagnose the issue.

@sdirix I cannot post the schema as it contains proprietary information. If I have time I might gen up a vanilla replica.

@sdirix Playing with this further, if I want to change the renderer for “oneOf” in the following schema:

{
  "type": "object",
  "properties": {
    "data": {
      "oneOf": [
        {
          "title": "Tab1",
          "type": "string"
        },
        {
          "title": "Tab2",
          "type": "string"
        },
        {
          "title": "Tab3",
          "type": "string"
        }
      ]
    }
  },
  "required": []
}

I can do so using a UI schema that contains this line:

{ type: "Control", scope: "#/properties/data/oneOf" },

And then create my renderer with a custom control and tester, registering it using:

{ tester: dataControlTester, renderer: DataControl }

The above works. However, if the schema is a bit more complex with multiple embedded “oneOf” params:

{
  "type": "object",
  "properties": {
    "data": {
      "oneOf": [
        {
          "type:": "object",
          "oneOf": [
            {
              "properties": {
                "data": {
                  "oneOf": [
                    {
                      "title": "Tab1",
                      "type": "string"
                    },
                    {
                      "title": "Tab2",
                      "type": "string"
                    },
                    {
                      "title": "Tab3",
                      "type": "string"
                    }
                  ]
                }
              }
            }
          ]
        }
      ]
    }
  }
}

Using

{ type: "Control", scope: "#/properties/data/oneOf/oneOf" },

Does not appear to work, presumably because it’s only doing a “shallow copy” of the schema and doesn’t recognize the second “oneOf”. So how do we create a custom renderer for the second “oneOf”?

Usually we use the scope #/properties/data here. By default the schema will then resolve to oneOf: [ //... ] which can be checked by testers.


The scope is mostly treated like a regular JSON Pointer, so it will just try to follow the segments. As #/properties/data/oneOf points to an array, it expects a number after that, e.g. #/properties/data/oneOf/0/properties/data would be a valid scope.

I wrote mostly because there is an exception in JSON Forms. To support JSON Schemas which model object inheritence we have some special resolving logic which allows you to skip combinators, i.e. the following should work #/properties/data/properties/data.

The altenative to this “special” logic is to use detail ui schemas. Any object or array renderer first checks whether there is a registered uischema for the details it wants to render. This detail can be handed over via the uischema directly, i.e. via the options.detail field, or via the uischemas registry.

These detail ui schemas are then scoped against the respective sub-schema, so they don’t need to contain the oneOf and index segments.

@sdirix I feel like I’m getting closer. Let me try to better explain what I want. For a schema similar to this much simplified one:

{
  "type": "object",
  "properties": {
    "myParams": {
      "title": "Parameters",
      "type": "object",
      "anyOf": [
        {
          "title": "Commands1",
          "type": "object",
          "anyOf": [
            {
              "title": "Title",
              "type": "object",
              "properties": {...}
            }
          ]
        },
        {
          "title": "Commands2",
          "type": "object",
          "anyOf": [...]
        }
      ]
    }
  }
}

I want the “anyOf” just for Commands2 to render as an MUI Select component, not as a Tab component. And I don’t want to generate a complete UI Schema to do this because, as I stated, this is a much simplified version of what I’m actually dealing with which is a JSON schema having over 2,000 lines so a UI Schema would also be very large and difficult to maintain.

Using some of your suggestions I was able to get a Select component rendered by using #/properties/myParams/anyOf/1/anyOf. What I want is for the elements of each array to appear in the Select component (because there are far too many to use a tab component) and, when one is selected, have the UI display those related parameters just as switching to another tab does. The problem is with having all these embedded anyOf and oneOf arrays (in the real schema they’re embedded even deeper than this). This is why I was trying to use #properties/myParams/anyOf/anyOf but that doesn’t appear to work, and specifying an array index limits me to just that one entry. Now if I could use something like #/properties/myParams/anyOf/*/anyOf that might help?

I’m sure there’s a way to do what I want but maybe not without creating a full UI schema?

Hi @DiskCrasher,

Many developers just use custom renderers to manage the overall rendering of more complex JSON Schemas. For example by registering a renderer with a higher priority than the existing anyOf renderer you can easily configure that all these cases a rendered with a dropdown instead of a tab.

As then there is no need to customize or even provide any UI Schema this is a popular approach for many use cases.


In case you want to keep the UI schema strategy you can take a look at the uischemas registry. It is consulted whenever a “detail” UI schema of an object or array shall be rendered. Therefore it’s useful whenever just parts of the overall rendering needs to be customized.

@sdirix I guess I missed the part where it was stated that you don’t need a custom UI schema to use a customer renderer. That simplifies things a bit. However, I’m still unable to get it to do what I want. When I write a tester that has scopeEndsWith("myParams/anyOf/1/anyOf") nothing changes on the UI. Likewise, using scopeEndsWith("myParams/anyOf/anyOf") or just scopeEndsWith("myParams/anyOf") also doesn’t render. Should I be using something else? Appreciate the help thus far.

Hi @DiskCrasher,

The auto-generated UI Schemas are all shallow, so you will never encounter a scope which contains anyOf etc. The scope will always end at the property name. The next sub-uischema will be resolved against the respective selected oneOf/oneOf/anyOf and therefore also don’t contain them.

I would recommend to debug or console.log in your tester all uischema.scope which you get to get a feeling for the mechanism in place.

@sdirix This seems like a limitation. If you can’t specify a JSON combinator in your custom renderer scope, how can you change how they are rendered? Currently the array elements under anyOf/oneOf are rendered as MUI Tabs. What if you wanted to render them as MUI Select components instead so that all array elements are shown in the Select dropdown and when you click on one, its related fields are displayed (same as when you click on a tab)? Is this even possible?

Hi @DiskCrasher,

Generally speaking there is no limitation in JSON Forms. All custom renderers have full control and can render arbitrary UI.

Yes. I was just explaining how the default UI generator works which will not include any anyOf/oneOf/allOf in its scopes.

See for example this JSON Schema

{
  "type": "object",
  "properties": {
    "person": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "contact": {
          "type": "object",
          "properties": {
            "phone": {
              "type": "string"
            },
            "email": {
              "type": "string"
            },
            "preferredContactMethod": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "enum": ["phone"]
                    },
                    "phone": {
                      "type": "string"
                    }
                  }
                },
                {
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "enum": ["email"]
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}

These are some of the then used generated UI Schemas:

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/person"
    }
  ]
}
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/name"
    },
    {
      "type": "Control",
      "scope": "#/properties/contact"
    }
  ]
}
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/phone"
    },
    {
      "type": "Control",
      "scope": "#/properties/email"
    },
    {
      "type": "Control",
      "scope": "#/properties/preferredContactMethod"
    }
  ]
}

Each of them is resolved against the respective sub-schema.

In your case your custom renderer would take over the rendering of this element

    {
      "type": "Control",
      "scope": "#/properties/preferredContactMethod"
    }

and render your desired UI, i.e. a dropdown for each oneOf entry and then delegate the rendering to the selected oneOf entry.

For example the UI Schema for the first oneOf entry will then look like this:

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/method"
    },
    {
      "type": "Control",
      "scope": "#/properties/phone"
    }
  ]
}

@sdirix Your example doesn’t quite address my schema structure which looks more like this:

{
  "type": "object",
  "properties": {
     "myParams": {
         "type": "object",
          "anyOf": [
             {
                 "type": "object",
                 "$schema": "http://json-schema.org/draft-04/schema#",
                 "title": "Title1",
                 "description": "Desc1",
                 "anyOf": [
                    "type": "object",
                    "$schema": "http://json-schema.org/draft-04/schema#",
                    "title": "Sub1",
                    "description": "SubDesc1",
                    "properties": { "item1": {...}, "item2": {...}, "item3": {...} }
                 ]
             },
             {
                "type": "object",
                "anyOf": [
                    "type": "object",
                    "$schema": "http://json-schema.org/draft-04/schema#",
                    "title": "Sub2",
                    "description": "SubDesc2",
                    "properties": { "item1": {...}, "item2": {...}, "item3": {...} }
                ]
             },
     },
}

I can use #/properties/myParms but how do I specify the second anyOf without having to drill down any further? I want the “title” of the elements in that array (“Sub1” & “Sub2”) to be displayed using a Select component, not a Tab component as they currently are by default, and I don’t want to specify in my scope each element in the array to do so (because then if the array elements change, so must my code). From the examples you’ve shown this doesn’t appear possible because you can’t specify anyOf in the scope. It’s unexpected that JSON Forms will generate a UI tab component for anyOf but there’s apparently no (easy) way to create a custom renderer for it.

Hi @DiskCrasher,

All you need is a custom AnyOf renderer which renders a dropdown instead of tabs. There is no need for any UI Schema at all then. Within this custom renderer you can simply use the UI Schema generator for the handed in schema and dispatch back to JSON Forms.

It’s only if you want to control the UI behavior via the UI Schema that you need to maintain one. Many users rely on the default UI Schema generation and do all customizations via custom renderers.

@sdirix Yeah I realize I don’t need a UI schema for this as I indicated in my post back on 7/1. But I still don’t see a way to access the second anyOf in my example above so I’m still dead in the water.