networknt / json-schema-validator

A fast Java JSON schema validator that supports draft V4, V6, V7, V2019-09 and V2020-12
Apache License 2.0
822 stars 323 forks source link

anyOf and OneOf unhelpful error messages #1090

Closed JBurning closed 1 month ago

JBurning commented 1 month ago

I found a new case where the error messages are far less helpful than what other programs can do. Here I am using anyOf to match for objects with different types of properties in a list. When I purposely use an incorrect type on one of my objects in my yaml, VSCode this shows the following very succinct error: image

But when I run this through this library, the message I'm hit with is this:

[.things[4].person: integer found, string expected, 
.things[4].person: integer found, string expected, 
.things[4]: required property 'fullAddress' not found, 
.things[4]: property 'person' is not defined in the schema and the schema does not allow additional properties, 
.things[4]: required property 'place' not found, 
.things[4]: property 'person' is not defined in the schema and the schema does not allow additional properties, 
.things[4]: required property 'group' not found, 
.things[4]: required property 'things' not found]

Which is just giving me all of the possible errors in all the schemas and then some instead of trying to match the object to the closest possible schema and then validating it using that alone. Is there a way around this or does this just need to get fixed?

Schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "things": {
      "type": ["array", "null"],
      "items": {
        "anyOf": [
          { "$ref": "#/$defs/basicPerson" },
          { "$ref": "#/$defs/advancedPerson" },
          { "$ref": "#/$defs/place" },
          {
            "type": "object",
            "additionalProperties": false,
            "required": ["group", "things"],
            "properties": {
              "group": {
                "type": "string"
              },
              "things": {
                "type": ["array", "null"],
                "items": {
                  "anyOf": [
                    { "$ref": "#/$defs/person" },
                    { "$ref": "#/$defs/basicPerson" },
                    { "$ref": "#/$defs/advancedPerson" },
                    { "$ref": "#/$defs/place" }
                  ]
                }
              }
            }
          }
        ]
      }
    }
  },
  "$defs": {
    "person": {
      "type": "string"
    },
    "phone": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "basicPerson": {
      "type": "object",
      "additionalProperties": false,
      "required": ["person"],
      "properties": {
        "person": { "$ref": "#/$defs/person" },
        "phone": { "$ref": "#/$defs/phone" },
        "country": { "type": "string" }
      }
    },
    "advancedPerson": {
      "type": "object",
      "additionalProperties": false,
      "required": ["person", "fullAddress"],
      "properties": {
        "person": { "$ref": "#/$defs/person" },
        "phone": { "$ref": "#/$defs/phone" },
        "fullAddress": { "type": "string" },
        "payment": { "type": "string" }
      }
    },
    "place": {
      "type": "object",
      "additionalProperties": false,
      "required": ["place"],
      "properties": {
        "place": {
          "type": "string"
        },
        "country": { "$ref": "#/$defs/country" },
        "touristSpots": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "name": { "type": "string" },
              "geolocation": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}

Sample:

---
things:
  - person: Jerry
  - person: Dave
    phone: "999-999-9999"
  - person: Steve
    fullAddress: "1234 Street, A1A 1A1, US"
    payment: "credit card"
  - place: Miami
    country: US
    touristSpots:
      - name: Wynwood Walls
        geolocation: "1281946 N 834671 W"
      - name: Little Havana
        geolocation: "89173 N 93782 W"
  - person: 1
justin-tay commented 1 month ago

This is expected behavior from a library. It is up to the caller how to decide how to display the messages. The messages should already contain sufficient context on the error, for instance you can get the value that caused the failure from the instanceNode or the relevant schema from schemaNode. You may find OutputFormat.HIERARCHICAL more helpful in visualising the error. You may also consider using if, then, else in your schema if you have a heuristic for determining which of the anyOf should likely match given your data.

JBurning commented 1 month ago

Thankfully my schema allowed me to resolve this using if then else statements to guide the validation. Thanks for the clarification.