2

In JSON schema, I want to add a validation that if the payload matches below

{
   "key1": "value1", 
   "key1": "value2"
}

Then expect a third key "key3" with any value.

But if the value of key1 and key2 doesn't match value1, value2 respectively then key3 should not be present.

JSON schema looks like this

{
  "$async": true,
  "additionalProperties": false,
  "properties": {
    "key1": {
      "type": "string"
    }, 
    "key2": {
      "type": "string"
    }
  },
  "required": ["key1"],
  "allOf": [
    {
      "if": {
        "properties": {
          "key1": {
            "const": "value1"
          }, 
          "key2": {
            "const": "value2"
          }
        }
      },
      "then": {
        "properties": {
          "key3": {
            "type": "string"
          }
        },
        "required": ["key3"]
      },
      "else": {
      }
    }
  ]
}

Valid inputs are

{
   "key1": "value1", 
   "key2": "value2", 
   "key3": "some-value"
}
--------
{
    "key1": "value1", 
    "key2": "other-value"
}
---------
{
    "key1": "value1"
}
---------
{
    "key1": "other-value", 
    "key2": "value2"
}
---------
{
    "key1": "other-value1", 
    "key2": "other-value2"
}

Invalid inputs are

{
   "key1": "value1", 
   "key2": "value2".   // key3 should be present
}
--------
{
   "key1": "hello", 
   "key2": "world",  
   "unexpected-key": "value"   // Any other key should not be allowed 
}
--------
{
    "key1": "value1", 
    "key2": "other-value", 
    "key3": "abc"    // should not be present
 
}
---------
{
    "key1": "other-value", 
    "key2": "value2",
    "key3": "abc"    // should not be present
}
---------
{
    "key1": "other-value1", 
    "key2": "other-value2",
    "key3": "abc"    // should not be present
}

For below payload, JSON schema validator

{
  "key1": "value1", 
  "key2": "value2", 
  "key3": "abc"
}

Throws below error

Property 'key3' has not been defined and the schema does not allow additional properties.

How do I achieve this conditional property?

Ganesh Satpute
  • 3,664
  • 6
  • 41
  • 78
  • Does this answer your question? [Express that, for a given property value, a property with the same name should exist using json schema?](https://stackoverflow.com/questions/55691332/express-that-for-a-given-property-value-a-property-with-the-same-name-should-e) – Relequestual Sep 24 '21 at 07:32
  • What I want are conditional properties. Isn't that different from what the question says? – Ganesh Satpute Sep 24 '21 at 09:48
  • My apologies, I misread the question. I'll recind my vote to close and provide you with an answer! =] – Relequestual Sep 24 '21 at 09:51
  • It's helpful if you include real values for your situation rather than `foo/bar` type values. Less likely to misread the problem =] – Relequestual Sep 24 '21 at 09:53
  • I personally think that adding real values makes problem harder to understand. As readers would have to understand the domain problem and then actual problem. Whereas if use generic values, it takes off the burden to understand the domain specific things. I think the examples given should be self explanatory – Ganesh Satpute Sep 24 '21 at 10:00
  • I find it sometimes obfuscates the real problem. I agree in this instance it's probably fine, but it can get confusing with "programming" terms (like "key") as data, rather than "non-programming" terms. Makes it easier to read, and I read a lot of them. *shrugs* personal preference. Might have meant you got an answer yesterday rather than today. I guess, as with a lot of things, it depends! – Relequestual Sep 24 '21 at 10:39
  • That makes sense @Relequestual. I would avoid terms like keys in the future and will update the answers so it help others. – Ganesh Satpute Sep 24 '21 at 10:58

1 Answers1

3

The solution depends on which version (or "draft") you're able to use of JSON Schema. Draft 2019-09 or above provides a cleaner solution, but it's still possible in draft-07.

(It's also possible before if/then/else was introduced using the implication method, but I won't go into that today.

Best solution using 2019-09 or 2020-12.

The unevaluatedProperties keyword can "see through" applicator keywords, like allOf, if and else.

Unlike draft-07's additionalProperties, unevaluatedProperties is run after all other applicators in the schema schema object, and can know about successfully validated property values defined in deeper nested applicators.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "unevaluatedProperties": false,
  "properties": {
    "key1": {
      "type": "string"
    },
    "key2": {
      "type": "string"
    }
  },
  "required": [
    "key1"
  ],
  "allOf": [
    {
      "if": {
        "required": ["key2"],
        "properties": {
          "key1": {
            "const": "value1"
          },
          "key2": {
            "const": "value2"
          }
        }
      },
      "then": {
        "required": ["key3"],
        "properties": {
          "key3": {
            "type": "string"
          }
        }
      }
    }
  ]
}

For testing draft 2019-09 and above, I recomend using https://json-schema.hyperjump.io playground

(https://jsonschema.dev doesn't yet support draft 2019-09 and above).

Solution using draft-07.

For additionalProperties: false to work, it needs to have the properties defined in the same schema object in properties (or match patternProperties).

Given in the first instance, you don't care about the specific value, the value in properties object is just true. The value validation is handled later. You could move it to the top level if you feel it's cleaner.

With this change, our if/then worked from your example, apart from it didn't prevent key3 when it should have. We fix that by making use of not inside else.

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "additionalProperties": false,
  "properties": {
    "key1": {
      "type": "string"
    }, 
    "key2": {
      "type": "string"
    },
    "key3": true
  },
  "required": ["key1"],
  "allOf": [
    {
      "if": {
        "required": ["key2"],
        "properties": {
          "key1": {
            "const": "value1"
          }, 
          "key2": {
            "const": "value2"
          }
        }
      },
      "then": {
        "required": ["key3"],
        "properties": {
          "key3": {
            "type": "string"
          }
        }
      },
      "else": {
        "not": {
          "required": ["key3"]
        }
      }
    }
  ]
}

Demo: https://jsonschema.dev/s/ZTi4X

Relequestual
  • 11,631
  • 6
  • 47
  • 83