Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally forbid properties based on presence of other properties in JSON Schema?

In my schema I declared these properties:

"index_name": {
      "type": "string",
      "examples": ["foo-wwen-live", "foo"]
    },
"locale": {
      "type": "string",
      "examples": ["wwen", "usen", "frfr"]
},
"environment": {
      "type": "string",
      "default": "live",
      "examples": [
        "staging",
        "edgengram",
        "test"
      ]
}

I want a JSON body validated against my schema to be valid only if:

  • index_name is present, and both locale and environment are not present;
  • locale and/or enviroment are present, and index_name is not present

In short, locale and environment should never be mixed with index_name.

Test cases and desired results:

These should pass:
Case #1

{
  "locale": "usen"
}

Case #2

{
  "environment": "foo"
}

Case #3

{
  "environment": "foo",
  "locale": "usen"
}

Case #4

{
  "index_name": "foo-usen"
}

These should NOT pass:
Case #5

{
  "index_name": "foo-usen",
  "locale": "usen"
}

Case #6

{
  "index_name": "foo-usen",
  "environment": "foo"
}

Case #7

{
  "index_name": "foo-usen",
  "locale": "usen",
  "environment": "foo"
}

I created the following rule for my schema, however it does not cover all the cases. For example, if both locale and environment are present, validation returns failure if index_name is also present, which is correct behavior according to case #7. But if only one of locale and environment is present, it allows index_name to also be present (fails at cases #5 and #6).

  "oneOf": [
    {
      "required": ["index_name"],
      "not": {"required":  ["locale", "environment"]}
    },
    {
      "anyOf": [
        {
          "required": ["locale"],
          "not": {"required": ["index_name"]}
        },
        {
          "required": ["environment"],
          "not": {"required": ["index_name"]}
        }
      ]
    }
  ]

I'm getting mixed information on how "not": {"required": []} declaration works. Some people claim this means that it forbids anything declared in the array to be present, in contrary to what idea does the syntax give. Other claim that this should be taken exactly as it sounds: properties listed in the array are not required - they can be present, but it doesn't matter if they aren't.

Apart from this rule, I also require one non-related property to be present in all cases and I set "additionalProperties": false.

What is the rule that would satisfy all my test cases?

like image 996
Maks Babarowski Avatar asked Apr 06 '20 12:04

Maks Babarowski


People also ask

What does $Ref mean in JSON schema?

In a JSON schema, a $ref keyword is a JSON Pointer to a schema, or a type or property in a schema. A JSON pointer takes the form of A # B in which: A is the relative path from the current schema to a target schema. If A is empty, the reference is to a type or property in the same schema, an in-schema reference.

What does Exclusiveminimum property in JSON schema mean?

Gets or sets a flag indicating whether the value can not equal the number defined by the minimum attribute (Minimum).

What is oneOf in JSON schema?

Here oneOf is a keyword construct in the JSON Schema, which is used to provide an array of criteria where, if exactly one of them is valid, the whole block is valid. As per the exampe above, objects having ( "email" AND "password" ) OR ( "username" AND "password" ) attributes are considered valid.

What is anyOf in JSON schema?

allOf: (AND) Must be valid against all of the subschemas. anyOf: (OR) Must be valid against any of the subschemas. oneOf: (XOR) Must be valid against exactly one of the subschemas.


1 Answers

Dependencies

This is a job for the dependencies keyword. The following says

  • if "locale" is present, then "index_name" is forbidden.
  • if "environment" is present, then "index_name" is forbidden.

|

"dependencies": {
  "locale": { "not": { "required": ["index_name"] } },
  "environment": { "not": { "required": ["index_name"] } }
}

What's up with not-required?

There's a sub question about how not-required works. It's confusing because it doesn't mean how it reads in English, but it's similar enough to make us think it does sometimes.

In the above example, if we read it as "not required", it sounds like it means "optional". A more accurate description would be "forbidden".

That's awkward, but not too bad. Where it gets confusing is when you want to "forbid" more than one property. Let's assume we want to say, if "foo" is present, then "bar" and "baz" are forbidden. The first thing you might try is this.

"dependencies": {
  "foo": { "not": { "required": ["bar", "baz"] } }
}

However, what this says is that if "foo" is present, then the instance is invalid if both "bar" AND "baz" are present. They both have to be there to trigger failure. What we really wanted is for it to be invalid if "bar" OR "baz" are present.

"dependencies": {
  "foo": {
    "not": {
      "anyOf": [
        { "required": ["bar"] },
        { "required": ["baz"] }
      ]
    }
  }
}

Why is this so hard?

JSON Schema is optimized for schemas that are tolerant to changes. The schema should enforce that the instance has a the necessary data to accomplish a certain task. If it has more than it needs, the application ignores the rest. That way, if something is add to the instance, everything still works. It shouldn't fail validation if the instance has a few extra fields that the application doesn't use.

So, when you try to do something like forbidding things that you could otherwise ignore, you're going a bit against the grain of JSON Schema and things can get a little ugly. However, sometimes it's necessary. I don't know enough about your situation to make that call, but I'd guess that dependencies is probably necessary in this case, but additionalProperties is not.

like image 161
Jason Desrosiers Avatar answered Oct 16 '22 00:10

Jason Desrosiers