Recursive rule?

I have a JSON Schema that includes a recursive type, and I’m trying to define the UI Schema accordingly. I looked through the documentation on applying UI Schema rules but wasn’t able to figure out how to adapt them to my use case.

The use case involves building an authorization rule set, where a rule can be either:

  • Simple: Represents a claim.
  • Recursive: A composite rule with “any” or “all” policies.

Depending on the rule type, certain controls should be shown or hidden dynamically. How can I structure my UI Schema to handle this?

Here is a sample JSON Schema:

{
  "definitions": {
    "authorizationRequirement": {
      "type": "object",
      "properties": {
        "type": {
          "description": "The type of authorization requirement to create",
          "type": "string",
          "enum": [
            "claim",
            "composite"
          ]
        },
        "claimType": {
          "description": "The type of the Claim to check. Supports regular expressions",
          "type": "string"
        },
        "claimValue": {
          "description": "The value of the Claim to check. Supports regular expressions. If no value is set, the requirement only checks for claim's existence",
          "type": "string"
        },
        "conditionType": {
          "description": "The type of condition to perform",
          "type": "string",
          "enum": [
            "all",
            "any"
          ]
        },
        "requirements": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/authorizationRequirement"
          }
        }
      }
    }
  },
  "type": "object",
  "properties": {
    "name": {
      "description": "The name of the authorization policy to create",
      "type": "string",
      "minLength": 1
    },
    "description": {
      "description": "The description of the authorization policy to create",
      "type": "string"
    },
    "requirements": {
      "description": "A list of authorization requirements",
      "type": "array",
      "items": {
        "$ref": "#/definitions/authorizationRequirement"
      }
    }
  },
  "required": [
    "name",
    "requirements"
  ]
}

and the UI Schema:

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/name"
    },
    {
      "type": "Control",
      "scope": "#/properties/description"
    },
    {
      "type": "Control",
      "scope": "#/properties/requirements",
      "options": {
        "detail": {
          "type": "VerticalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/type"
            },
            {
              "type": "HorizontalLayout",
              "rule": {
                "effect": "SHOW",
                "condition": {
                  "type": "LEAF",
                  "scope": "#/properties/type",
                  "expectedValue": "claim"
                }
              },
              "elements": [
                {
                  "type": "Control",
                  "scope": "#/properties/claimType"
                },
                {
                  "type": "Control",
                  "scope": "#/properties/claimValue"
                }
              ]
            },
            {
              "type": "VerticalLayout",
              "rule": {
                "effect": "SHOW",
                "condition": {
                  "type": "LEAF",
                  "scope": "#/properties/type",
                  "expectedValue": "composite"
                }
              },
              "elements": [
                {
                  "type": "Control",
                  "scope": "#/properties/conditionType"
                },
                {
                  "type": "Control",
                  "scope": "#/properties/requirements"
                }
              ]
            }
          ]
        }
      }
    }
  ]
}

Hi @JBBianchi,

For this you can use the uischemas registry. Thereby you will be able to return the same customized UI Schemas, no matter how deep they are.

So in your case you would use the following UI Schema and uischemas registry:

UI Schema

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/name"
    },
    {
      "type": "Control",
      "scope": "#/properties/description"
    },
    {
      "type": "Control",
      "scope": "#/properties/requirements"
    }
  ]
}

uischemas registry

const uischemas = [
  {
    tester: (_schema, schemaPath, _path) => {
      return schemaPath === '#/properties/requirements' ? 10 : NOT_APPLICABLE;
    },
    uischema: {
      type: 'VerticalLayout',
      elements: [
        {
          type: 'Control',
          scope: '#/properties/type',
        },
        {
          type: 'HorizontalLayout',
          rule: {
            effect: 'SHOW',
            condition: {
              type: 'LEAF',
              scope: '#/properties/type',
              expectedValue: 'claim',
            },
          },
          elements: [
            {
              type: 'Control',
              scope: '#/properties/claimType',
            },
            {
              type: 'Control',
              scope: '#/properties/claimValue',
            },
          ],
        },
        {
          type: 'VerticalLayout',
          rule: {
            effect: 'SHOW',
            condition: {
              type: 'LEAF',
              scope: '#/properties/type',
              expectedValue: 'composite',
            },
          },
          elements: [
            {
              type: 'Control',
              scope: '#/properties/conditionType',
            },
            {
              type: 'Control',
              scope: '#/properties/requirements',
            },
          ],
        },
      ],
    }
  },
];

Then in the code later

<JsonForms
  { ...otherProps }
  uischemas={uischemas}
/>

Note that you could also model this directly into the JSON Schema, without using UI Schemas at all. The benefit would be that you can validate for the correct data later on, which you can’t when using UI Schemas.

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "title": "Name"
    },
    "description": {
      "type": "string",
      "title": "Description"
    },
    "requirements": {
      "type": "array",
      "title": "Requirements",
      "items": {
        "$ref": "#/definitions/authorizationRequirement"
      }
    }
  },
  "required": ["name", "requirements"],
  "definitions": {
    "authorizationRequirement": {
      "oneOf": [
        {
          "$ref": "#/definitions/claimRequirement"
        },
        {
          "$ref": "#/definitions/compositeRequirement"
        }
      ]
    },
    "claimRequirement": {
      "type": "object",
      "title": "Claim",
      "properties": {
        "type": {
          "const": "claim",
          "default": "claim",
          "title": "Type"
        },
        "claimType": {
          "type": "string",
          "title": "Claim Type"
        },
        "claimValue": {
          "type": "string",
          "title": "Claim Value"
        }
      },
      "required": ["type", "claimType", "claimValue"]
    },
    "compositeRequirement": {
      "type": "object",
      "title": "Composite",
      "properties": {
        "type": {
          "const": "composite",
          "default": "composite",
          "title": "Type"
        },
        "conditionType": {
          "type": "string",
          "enum": ["AND", "OR"],
          "default": "AND",
          "title": "Condition Type"
        },
        "requirements": {
          "type": "array",
          "title": "Requirements",
          "items": {
            "$ref": "#/definitions/authorizationRequirement"
          }
        }
      },
      "required": ["type", "conditionType", "requirements"]
    }
  }
}

Using this, you don’t even need any UI Schema, although the UI looks then different by default.

PureJsonSchema

Thank for your reply @sdirix !