Add rules to nested structures

Hi,
is there a way to add rules to nested elements? eg I would like to show the child when the haschild checkbox is ticked?
Bonus question: how to make the checkbox into a slider?
Thanks, Bart

{
  "definitions": {
    "item": {
      "type": "object",
      "properties": {
        "hasChild": {
          "type": "boolean",
          "title": "hasChild"
        },
        "child": {
          "title": "child",
          "$ref": "#/definitions/item"
        }
      }
    }
  },
  "title": "nested example",
  "type": "object",
  "properties": {
    "parent": {
      "title": "parent",
      "$ref": "#/definitions/item"
    }
}

Hi @BartJansseune,

You can use the following UI Schema for the toogle and the rule:

{
  "type": "Control",
  "scope": "#",
  "options": {
    "detail": {
      "type": "VerticalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/parent/properties/hasChild",
          "options": {
            "toggle": true
          }
        },
        {
          "type": "Control",
          "scope": "#/properties/parent/properties/child",
          "rule": {
            "effect": "SHOW",
            "condition": {
              "scope": "#/properties/parent/properties/hasChild",
              "schema": {
                "const": true
              }
            }
          }
        }
      ]
    }
  }
}

Note however that there is no way of adding a “child” object as we don’t support adding explicit nested objects outside of an array by default. We only support this by rendering nested controls which then add the object implicitly. This works in all use cases except for the self-referential one of yours as this approach would lead to an endless loop.

If you need to support this then you need to add a custom renderer which offers to add such a single nested object. This should be straightforward to implement.

However what we support is adding nested objects in an array, so you could change your schema to this:

{
  "definitions": {
    "item": {
      "type": "object",
      "properties": {
        "hasChild": {
          "type": "boolean",
          "title": "hasChild"
        },
        "child": {
          "title": "child",
          "type": "array",
          "items": {
            "$ref": "#/definitions/item"
          }
        }
      }
    }
  },
  "title": "nested example",
  "type": "object",
  "properties": {
    "parent": {
      "title": "parent",
      "$ref": "#/definitions/item"
    }
  }
}

To support the “hide” functionality in all nested cases you can’t obviously use the “detail” approach as it would require indefinite nesting. What you can do instead is to use the “uischemas” registry. This registry will be queried for each nested object for a Ui Schema. There you can then generate and return a UI Schema with a rule as above, just with all scopes adjusted to the current nested level. Alternatively you could of course also just implement a custom renderer which is able to look up the sibling hasChild to determine whether it needs to be hidden.

Hi Stefan,
thanks for your feedback and the time you put in.
In my application, I would not really have nesting, but “items” that exist at different levels of data hierarchy.

{
  "definitions": {
    "item": {
      "type": "object",
      "properties": {
        "IsEnabled": {
          "type": "boolean",
          "title": "IsEnabled"
        },
        "ifEnabledString": {
          "type": "string",
          "title": "ifEnabledString"
        }
      }
    }
  },
  "type": "object",
  "properties": {
    "level0": {
      "title": "level0",
      "$ref": "#/definitions/item"
    },
    "level1": {
      "type": "object",
      "properties": {
        "level1": {
          "title": "level1",
          "$ref": "#/definitions/item"
        }
      }
    }
  }
}

I know how I should add the rules, but I really would like to avoid, for maintainability reasons, having to mimic the whole datastructure in UIschema just in order to be able to add rules to certain elements.
I have seen some uses of UIschema’s but I didn’t find any proper explaination on how they should be used and what problems they solve.
Regards,
Bart

I tryed this (uischeams.json):

{
  "tester": "(jsonSchema, schemaPath, path) => { console.log(schemaPath); console.log(path); console.log(jsonSchema); return jsonSchema.$id === '#/definitions/item' ? 2 : -1; }",
  "uischema": {
    "type": "HorizontalLayout",
    "elements": [
      {
        "type": "Control",
        "scope": "#/properties/IsEnabled",
        "options": {
          "format": "slider"
        }
      },
      {
        "type": "Control",
        "scope": "#/properties/ifEnabledString",
        "rule": {
          "effect": "SHOW",
          "condition": {
            "scope": "#/properties/IsEnabled",
            "schema": true
          }
        }
      }
    ],
    "options": {}
  }
}

and added it in App.ts:
import uischemas from ‘./uischemas.json’;

import uischemas from './uischemas.json';

....
            <JsonForms
              schema={schema}
              uischema={uischema}
              uischemas={uischemas}
              data={data}


Results in following error:

TS2740: Type '{ tester: string; uischema: { type: string; elements: ({ type: string; scope: string; options: { format: string; }; rule?: undefined; } | { type: string; scope: string; rule: { effect: string; condition: { scope: string; schema: boolean; }; }; options?: undefined; })[]; options: {}; }; }' is missing the following properties from type 'JsonFormsUISchemaRegistryEntry[]': length, pop, push, concat, and 29 more.
    120 |               data={data}
    121 |               renderers={renderers}
  > 122 |               uischemas={uischemas}
        |               ^^^^^^^^^
    123 |               cells={materialCells}

I did not yet look into the rest of the question, however this error occurs because uischemas expects an array of entries, not a single entry.

I also tried that but results also in error:

ERROR in src/App.tsx:119:15
TS2741: Property 'type' is missing in type '{ type: string; scope: string; options: { detail: { type: string; elements: ({ type: string; scope: string; options?: undefined; } | { type: string; scope: string; options: { toggle: boolean; }; })[]; }; }; }[]' but required in type 'UISchemaElement'.
    117 |             <JsonForms
    118 |               schema={schema}
  > 119 |               uischema={uischema}
        |               ^^^^^^^^
    120 |               data={data}
    121 |               renderers={renderers}
    122 |               uischemas={uischemas}

ERROR in src/App.tsx:122:15
TS2740: Type '{ tester: string; uischema: { type: string; elements: ({ type: string; scope: string; options: { format: string; }; rule?: undefined; } | { type: string; scope: string; rule: { effect: string; condition: { scope: string; schema: boolean; }; }; options?: undefined; })[]; options: {}; }; }' is missing the following properties from type 'JsonFormsUISchemaRegistryEntry[]': length, pop, push, concat, and 29 more.
    120 |               data={data}
    121 |               renderers={renderers}
  > 122 |               uischemas={uischemas}
        |               ^^^^^^^^^
    123 |               cells={materialCells}
    124 |               onChange={({ errors, data }) => validateData(data)}
    125 |               ajv={handleDefaultsAjv}

uischema is an object and uischemas an array

Thanks for pointing this out, I accidently made uischema into an array and not uischemas.
However after correcting I still get:

TS2322: Type '{ tester: string; uischema: { type: string; elements: ({ type: string; scope: string; options: { format: string; }; rule?: undefined; } | { type: string; scope: string; rule: { effect: string; condition: { scope: string; schema: boolean; }; }; options?: undefined; })[]; options: {}; }; }[]' is not assignable to type 'JsonFormsUISchemaRegistryEntry[]'.
  Type '{ tester: string; uischema: { type: string; elements: ({ type: string; scope: string; options: { format: string; }; rule?: undefined; } | { type: string; scope: string; rule: { effect: string; condition: { scope: string; schema: boolean; }; }; options?: undefined; })[]; options: {}; }; }' is not assignable to type 'JsonFormsUISchemaRegistryEntry'.
    Types of property 'tester' are incompatible.
      Type 'string' is not assignable to type 'UISchemaTester'.

obviously “tester” in uischemas.json is a string. So how to convert it to a UISchemaTester?

Well the tester is intended to be a function. So you can’t serialize it out into JSON or you need to do an eval pass on your string to construct the function before handing it over.

I’ll give it a try tonight. The weird thing is that in the vue variant, there is no need for this.
Do you know if “rules” inside uischemas should work?

I got it to work with eval!

  let myuischemas = uischemas.map((m_uischema) => ({
    tester: eval(m_uischema.tester),
    uischema: m_uischema.uischema,
  }));

2 other mistakes corrected:

    "item": {
      "type": "object",
      "$id": "#/definitions/item",
      "properties": {
        "IsEnabled": {
          "type": "boolean",
          "title": "IsEnabled"
        },
        "ifEnabledString": {
          "type": "string",
          "title": "ifEnabledString"
        }
      }
    },

and

       {
          "type": "Control",
          "scope": "#/properties/IsEnabled",
          "options": {
            "toggle": true
          }
        },
        {
          "type": "Control",
          "scope": "#/properties/ifEnabledString",
          "rule": {
            "effect": "SHOW",
            "condition": {
              "scope": "#/properties/IsEnabled",
              "schema": {
                "const": true
              }
            }
          }
        }

Thanks for the help