json-schema-validator icon indicating copy to clipboard operation
json-schema-validator copied to clipboard

if-then-else condition evaluates to true when property undefined

Open danic opened this issue 4 years ago • 5 comments

Hi,

I don't think if-then-else in combination with allOf is working as intended when the property specified in the condition is undefined. All conditions seem to evaluate to true and every single then block is executed. For instance using the example here: https://json-schema.org/understanding-json-schema/reference/conditionals.html

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "street_address": {
      "type": "string"
    },
    "country": {
      "enum": ["United States of America", "Canada", "Netherlands"]
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "country": { "const": "United States of America" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": "Canada" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": "Netherlands" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
      }
    }
  ]
}

the following Json is successfully validated as expected:

{
  "street_address": "Adriaan Goekooplaan",
  "country": "Netherlands",
  "postal_code": "2517 JX"
}

but if I remove the country:

{
  "street_address": "Adriaan Goekooplaan",
  "postal_code": "abc"
}

then I get the following list of errors showing every then was evaluated:

    $.postal_code: does not match the regex pattern [0-9]{5}(-[0-9]{4})?,
    $.postal_code: does not match the regex pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9],
    $.postal_code: does not match the regex pattern [0-9]{4} [A-Z]{2}]

I can make country required but then I get:

    $.country: is missing but it is required,
    $.postal_code: does not match the regex pattern [0-9]{5}(-[0-9]{4})?,
    $.postal_code: does not match the regex pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9],
    $.postal_code: does not match the regex pattern [0-9]{4} [A-Z]{2}]

danic avatar Mar 13 '21 09:03 danic

@danic Thanks a lot for raising the issue. This is a defect when combining if-then with allOf as they work within a different context as a standalone validator. Have you debugged into it and figured out the root cause?

stevehu avatar Mar 14 '21 13:03 stevehu

Root cause seems to be in com.networknt.schema.PropertiesValidator#validate where in this case propertyNode is null and the code will return an empty list of error messages. This is interpreted further up the stack in com.networknt.schema.IfValidator#validate as a success. Adding an else in com.networknt.schema.PropertiesValidator#validate to return an error when propertyNode is null breaks quite a few unit tests. Would require somebody familiar with the code to apply a proper fix.

danic avatar Mar 14 '21 19:03 danic

@danic this is interesting... I'm trying to learn json-schema and tried your test case above in some other java libraries (everitorg & vertx), plus an online one (https://www.jsonschemavalidator.net/) and they all return the same error when there is no country as this library!

properties doesn't imply a property is mandatory, so I'm thinking maybe the if passing validation into the then when there is no country is correct? If I add "required" to the if blocks...

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "street_address": {
      "type": "string"
    },
    "country": {
      "enum": ["United States of America", "Canada", "Netherlands"]
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "country": { "const": "United States of America" } },
        "required": ["country"]
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": "Canada" } },
        "required": ["country"]
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": "Netherlands" } },
        "required": ["country"]
      },
      "then": {
        "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
      }
    }
  ]
}

then I find that this does pass validation without any errors

{
  "street_address": "Adriaan Goekooplaan",
  "postal_code": "abc"
}

gareth-robinson avatar Nov 23 '21 15:11 gareth-robinson

I see this is called out as expected behaviour in the documentation at the end of this section: https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else "Note. The “required” keyword is necessary in the “if” schemas or they would all apply if the “country” is not defined"

gareth-robinson avatar Nov 23 '21 15:11 gareth-robinson

@gareth-robinson Good findings. When combining multiple validators together, the situation is much more complicated. The specification team needs to manage the complicity, otherwise, the specification would be really hard to implement.

stevehu avatar Nov 24 '21 20:11 stevehu

As @gareth-robinson noted, this is expected behavior as explained in the example's source.

fdutton avatar Jun 11 '23 20:06 fdutton