jsonforms icon indicating copy to clipboard operation
jsonforms copied to clipboard

indexOfFittingSchema for oneOf doesn't work anymore if $ref is used

Open Fankhauser-Dominik opened this issue 3 years ago • 2 comments

Describe the bug

Since Version 3.0.0-beta.2, the indexOfFittingSchema returns undefined instead, for example 0 or 1. The Previous Versions worked fine.

If I use the same schema without any refs, it works fine.

oneOf with $ref (official oneOf Example)
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street_address": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "state": {
          "type": "string"
        }
      },
      "required": [
        "street_address",
        "city",
        "state"
      ],
      "additionalProperties": false
    },
    "user": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "mail": {
          "type": "string"
        }
      },
      "required": [
        "name",
        "mail"
      ],
      "additionalProperties": false
    }
  },
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "addressOrUser": {
      "oneOf": [
        {
          "$ref": "#/definitions/address"
        },
        {
          "$ref": "#/definitions/user"
        }
      ]
    }
  },
  "required": [
    "name"
  ]
}
oneOf without $ref
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "addressOrUser": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "street_address": {
              "type": "string"
            },
            "city": {
              "type": "string"
            },
            "state": {
              "type": "string"
            }
          },
          "required": [
            "street_address",
            "city",
            "state"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "mail": {
              "type": "string"
            }
          },
          "required": [
            "name",
            "mail"
          ],
          "additionalProperties": false
        }
      ]
    }
  },
  "required": [
    "name"
  ]
}

Expected behavior

If the bound data matches a oneOf Schema, then it should have an indexOfFittingSchema.

Steps to reproduce the issue

  1. Go to https://codesandbox.io/s/vigilant-rgb-unncn8?file=/src/schema.json
  2. Because of the bound data it should render the ONEOF-1 Tab, but it renders the ONEOF-0 Tab (default behavior).
  3. Change the JsonForms versions to 3.0.0-beta.1
  4. You will see, that it now works properly

Screenshots

No response

In which browser are you experiencing the issue?

Browser independent

Framework

Core

RendererSet

Material

Additional context

No response

Fankhauser-Dominik avatar Jul 28 '22 12:07 Fankhauser-Dominik

I've bisected and stepped through the code a bit and found the culprit in #1829.

Before the merge mapStateToCombinatorRendererProps used the following logic to find the fitting subschema:

  ...
  const resolvedSchema = Resolve.schema(
    ownProps.schema || rootSchema,
    uischema.scope,
    rootSchema
  );
  ...
  const schema = resolvedSchema || rootSchema;
  const _schema = resolveSubSchemas(schema, rootSchema, keyword);
  ...
  let indexOfFittingSchema: number;
  // TODO instead of compiling the combinator subschemas we can compile the original schema
  // without the combinator alternatives and then revalidate and check the errors for the
  // element
  for (let i = 0; i < _schema[keyword].length; i++) {
    try {
      const valFn = ajv.compile(_schema[keyword][i]);
      valFn(data);
      if (dataIsValid(valFn.errors)) {
        indexOfFittingSchema = i;
        break;
      }
    } catch (error) {
      console.debug("Combinator subschema is not self contained, can't hand it over to AJV");
    }
  }

which works, because _schema contains full schemas that can be compiled. For reference, in the example by @Fankhauser-Dominik this is:

{
    "oneOf": [
        {
            "type": "object",
            "properties": {
                "street_address": {
                    "type": "string"
                },
                "city": {
                    "type": "string"
                },
                "state": {
                    "type": "string"
                }
            },
            "required": [
                "street_address",
                "city",
                "state"
            ],
            "additionalProperties": false
        },
        {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "mail": {
                    "type": "string"
                }
            },
            "required": [
                "name",
                "mail"
            ],
            "additionalProperties": false
        }
    ]
}

After #1829 was merged, the logic was changed to

  const resolvedSchema = Resolve.schema(
    ownProps.schema || rootSchema,
    uischema.scope,
    rootSchema
  );
  ...
  let indexOfFittingSchema: number;
  // TODO instead of compiling the combinator subschemas we can compile the original schema
  // without the combinator alternatives and then revalidate and check the errors for the
  // element
  for (let i = 0; i < resolvedSchema[keyword]?.length; i++) {
    try {
      const valFn = ajv.compile(resolvedSchema[keyword][i]);
      valFn(data);
      if (dataIsValid(valFn.errors)) {
        indexOfFittingSchema = i;
        break;
      }
    } catch (error) {
      console.debug("Combinator subschema is not self contained, can't hand it over to AJV");
    }
  }

This fails with the given debug message as resolvedSchema is now:

{
    "oneOf": [
        {
            "$ref": "#/definitions/address"
        },
        {
            "$ref": "#/definitions/user"
        }
    ]
}

golinski avatar Aug 05 '22 09:08 golinski

Thanks for the analysis! We'll take a look.

In case you need a workaround until the fix is done you can use json-refs to resolve your JSON Schemas before handing them to JSON Forms.

sdirix avatar Aug 08 '22 05:08 sdirix

Within the combinator mappings we try to determine the best fitting schema so that the renderer can decide which tab to show. With the removal of the auto-resolving this no longer worked in cases where the combinator contained references.

The situation is now improved by resolving references on the combinator level. This should help in many use cases but still does not restore the old behavior completely. Fully checking and resolving the whole sub schema is out of the scope with the new simplified resolving architecture. If that is needed the user either needs use a custom renderer or restore the old behavior by fully resolving their JSON Schema before handing it over to JSON Forms, for example via json-refs.

Fixed in https://github.com/eclipsesource/jsonforms/pull/2001

sdirix avatar Aug 19 '22 19:08 sdirix