Conditional Schema based on if then

Hi *,

we currently try to use a conditional schema by using if ... then ....

We have two types of servers (“publicCloud” and “privateCloud”). Both have the same property “cpuCount” but for the privateCloud we set a maximum of 5 CPU and for the publicCloud Server we set a maximum of 2 CPU. They also have different descriptions in the JsonSchema.

This example is extremly simplified, our real example is much more complex.

JsonSchema

{
  "type": "object",
  "properties": {
    "cloudType": {
      "type": "string",
      "default": "yes",
      "enum": [
        "private",
        "public"
      ]
    },
    "infrastructure": {
      "allOf": [
        {
          "if": {
            "properties": {
              "cloudType": {
                "const": "private"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/privateCloud"
          }
        },
        {
          "if": {
            "properties": {
              "cloudType": {
                "const": "public"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/publicCloud"
          }
        }
      ]
    }
  },
  "$defs": {
    "publicCloud": {
      "type": "object",
      "title": "Public Cloud Server",
      "properties": {
        "cpuCount": {
          "type": "number",
          "description": "cpu count on PUBLIC cloud",
          "maximum": 2
        }
      }
    },
    "privateCloud": {
      "type": "object",
      "title": "Private Cloud Server",
      "properties": {
        "cpuCount": {
          "type": "number",
          "description": "cpu count on PRIVATE cloud",
          "maximum": 5
        }
      }
    }
  }
}

In our uiSchema we have a dropdown (enum) to select the cloud type.

When the cloudType is “private” it should use

  • the maximum value: 5
  • and description: cpu count on PRIVATE cloud

When the cloudType is “public” it should use

  • the maximum value: 2
  • and description: cpu count on PUBLIC cloud

Ui Schema

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/cloudType"
    },
    {
      "type": "Control",
      "scope": "#/properties/infrastructure/properties/cpuCount",
      "options": {
        "showUnfocusedDescription": true
      }
    }
  ]
}

But the result we get is different:

  • the form is always showing us the description of the private cloud object
  • the form also only uses the maximum of the private cloud object

Is there any solution that jsonforms resolves the schema correctly?

Best Regards
Sebastian

1 Like

JSON Forms is not architectured in a way to support this feature request. When a Control is specified in the UI Schema then we simply resolve the given scope and only look at the resolved subschema.

Some versions ago the Control with #/properties/infrastructure/properties/cpuCount would have thrown an error as this is actually not a valid pointer for the given JSON Schema. In the mean time we made the resolving more flexible, searching through all then/else/anyOf/allOf/oneOf for resolving, however without actually evaluating them. For certain structures of schemas which try to model elements in an object oriented way this is very useful.

Therefore the behavior you see is the expected one. The given Control tries to resolve the scope as best as it can, following the #/$defs/privateCloud as it encounters it first and is then able to resolve. There is no evaluation at runtime and therefore you will always see that one being rendered.

Note that this restriction only exists for the rendering part. Validation is more feature rich as we are using AJV internally which supports all of JSON Schema’s features.

What we do support are special renderers for oneOf/anyOf/allOf. So if you point a Control directly to one of these, then a special renderer is invoked which is able to switch between the entries of oneOf/anyOf and collect all sub entries of allOf. Therefore you also get the dynamic behavior you want there.


Note that the given JSON Schema is probably not doing what you want, e.g. the following data

{
  cloudType: 'private',
  infrastructure: {cpuCount: 4}
}

will throw a validation error. I would recommend playing around with the schema in a validator with immediate feedback like this one.

The problem is that the if conditions are nested below infrastructure. Therefore they don’t check the cloudType of the root object, but they check whether infrastructure itself has a cloudType property. Also the if clauses don’t have a required specified. Therefore if cloudType is not specified for infrastructure then both if clauses will apply, e.g. the following data

{
  infrastructure: {cpuCount: 8}
}

will actually complain about both maximum being invalid.

This is another problem. Once you use a tool with default support, the value of cloudType will be set to 'yes'. This is not a valid entry of the enum and therefore will produce errors.


I would restructure the schema like this

{
  "type": "object",
  "properties": {
    "cloudType": {
      "type": "string",
      "enum": ["private", "public"]
    },
    "infrastructure": {
      "properties": {
        "cpuCount": {
          "type": "integer"
        }
      }
    }
  },
  "required": ["cloudType"],
  "allOf": [
    {
      "if": {
        "properties": {
          "cloudType": {
            "const": "private"
          }
        },
        "required": ["cloudType"]
      },
      "then": {
        "properties": {
          "infrastructure": {
            "properties": {
              "cpuCount": {
                "maximum": 5
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "cloudType": {
            "const": "public"
          }
        },
        "required": ["cloudType"]
      },
      "then": {
        "properties": {
          "infrastructure": {
            "properties": {
              "cpuCount": {
                "maximum": 2
              }
            }
          }
        }
      }
    }
  ]
}

Then you can keep using the existing UI Schema

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/cloudType"
    },
    {
      "type": "Control",
      "scope": "#/properties/infrastructure/properties/cpuCount",
      "options": {
        "showUnfocusedDescription": true
      }
    }
  ]
}

leading to the following result

DynamicValidation