RicoSuter / NJsonSchema

JSON Schema reader, generator and validator for .NET
http://NJsonSchema.org
MIT License
1.4k stars 535 forks source link

oneOf validation failing with valid data #1596

Open Droxx opened 1 year ago

Droxx commented 1 year ago

I have discovered an interesting problem in NJsonSchema validation.

For some context, I am using NJsonSchema to validate data coming in from a json-forms implementation. I won't include my exact schema and data here, but I wrote a unit test and managed to replicate this behavior with some test data, please see below:

Schema:

{
  "type": "object",
  "required": [
    "USER"
  ],
  "properties": {
    "$schema": {
      "type": "string"
    },
    "USER": {
      "type": "string"
    },
    "PASSWORD": {
      "type": "string"
    },
    "SINGLE_CHOICE": {
      "type": "string",
      "title": "Single choice",
      "description": "Some description",
      "oneOf": [
        {
          "const": "Yes",
          "title": "Yes"
        },
        {
          "const": "No",
          "title": "No"
        }
      ]
    },
    "MULTI_CHOICE": {
      "type": "array",
      "title": "Multi choice",
      "description": "Some description",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "oneOf": [
          {
            "const": "A",
            "title": "A"
          },
          {
            "const": "B",
            "title": "B"
          },
          {
            "const": "C",
            "title": "C"
          }
        ]
      }
    }
  },
  "additionalProperties": false
}

Data:

{
  "$schema": "./schema.json",
  "USER": "value",
  "SINGLE_CHOICE": "Yes",
  "MULTI_CHOICE": ["A", "C"] 
}

Using NJsonSchema fails to validate this data, specifically on the oneOf properties. Each time I get an error back from the validator saying that the provided data is not oneOf the available options. Json validation failed: NotOneOf: #/SINGLE_CHOICE I have run the above example through NewtonSoft's own validator, and it passes validation. However, the nuget package (10.9.0), fails.

I stepped through the validation code in order to try diagnosing this and I noticed that the oneOf options in the Schema object are stored in the ExtensionData property, which is never checked during validation, so every provided option matches, this, then causes a failure in the oneOf validator, since it's not getting any fails (the oneOf validation code returns valid only if there are n-1 errors, where n is the number of oneOf options)

        private void ValidateOneOf(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors)
        {
            if (schema._oneOf.Count > 0)
            {
                var propertyErrors = schema._oneOf.ToDictionary(s => s, s => Validate(token, s)); <--- In my example, validate always has 0 errors
                if (propertyErrors.Count(s => s.Value.Count == 0) != 1)
                {
                    errors.Add(new ChildSchemaValidationError(ValidationErrorKind.NotOneOf, propertyName, propertyPath, propertyErrors, token, schema));
                }
            }
        }

I believe this is a bug in the nuget package, since I have validated this data online using newtonsoft. JsonSchema.Net also correctly validates this data.

mindisk commented 11 months ago

Are there any news regarding this issue?

From react-json-schema-forms V5 the enumName will be taken on a depreciation path (https://rjsf-team.github.io/react-jsonschema-form/docs/migration-guides/v5.x%20upgrade%20guide/#non-standard-enumnames-property).

The oneOf will be the new way of defining Enum values and names. Thus, this bug becomes blocker for future react-json-schema-forms upgrades.

Droxx commented 11 months ago

Unfortunately due to this issue being a blocker, I had to migrate my software over to JsonSchema.net. So I have not investigated any further.

mindisk commented 11 months ago

A walkaround seem to be to use anyOfwhen defining constants

"anyOf": [
    {
       "const": "A",
        "title": "A"
    },
    {
       "const": "B",
       "title": "B"
    },
    {
       "const": "C",
       "title": "C"
    }

According to react-json-schema-forms documentation it is also a viable option and produces same result. At least it worked for me and the validation worked. Well, at least it seems so, for now.