8

I am using the following schema to validate my json:

{
    "$schema": "http://json-schema.org/schema#",
    "title": " Rules",
    "description": "Describes a set of rules",
    "type": "object",
    "properties": {
        "rules": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "precedence": {
                        "type": "number",
                        "minimum": 0
                    },
                    "conditions": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "field": {
                                    "type": "string",
                                    "enum": [ "Name", "Size" ]
                                },
                                "relation": {
                                    "type": "string",
                                    "enum": [ "is", "is not", "is not one of", "is one of" ]
                                },
                                "value": {
                                    "type": ["array", "string", "number"]
                                }
                            },
                            "required": ["field", "relation", "value"],
                            "additionalProperties": false
                        }
                    }                       
                },
                "required": ["precedence", "conditions"],
                "additionalProperties": false
            }
        }
    },
    "required": ["rules"],
    "additionalProperties": false
}

I want to set up a dependency to validate that when the value of the relation property has the value is one of or the value is not one of, then the type of the value property can only be array

For example, the following json should not validate because it uses the relation value is not one of and the value property is not an array:

{
    "rules": [{
            "precedence": 0,
            "conditions": [{
                    "field": "Name",
                    "relation": "is not one of",
                    "value": "Mary"
                }
            ]
        }
    ]
}

Is it possible to set up dependencies to validate this way?

Joel Harris
  • 1,966
  • 3
  • 20
  • 32

3 Answers3

11

The best way to solve these kinds of problems is to separate the complex validation from the rest of the schema using definitions and include it with an allOf. In this solution, I use implication to enforce the validation.

{
  "type": "object",
  "properties": {
    "rules": {
      "type": "array",
      "items": { "$ref": "#/definitions/rule" }
    }
  },
  "required": ["rules"],
  "definitions": {
    "rule": {
      "type": "object",
      "properties": {
        "precedence": { "type": "number", "minimum": 0 },
        "conditions": {
          "type": "array",
          "items": { "$ref": "#/definitions/condition" }
        }
      },
      "required": ["precedence", "conditions"]
    },
    "condition": {
      "type": "object",
      "properties": {
        "field": { "enum": ["Name", "Size"] },
        "relation": { "enum": ["is", "is not", "is not one of", "is one of"] },
        "value": { "type": ["array", "string", "number"] }
      },
      "required": ["field", "relation", "value"],
      "allOf": [{ "$ref": "#/definitions/array-condition-implies-value-is-array" }]
    },
    "array-condition-implies-value-is-array": {
      "anyOf": [
        { "not": { "$ref": "#/definitions/is-array-condition" } },
        { "$ref": "#/definitions/value-is-array" }
      ]
    }
    "is-array-condition": {
      "properties": {
        "relation": { "enum": ["is not one of", "is one of"] }
      },
      "required": ["relation"]
    },
    "value-is-array": {
      "properties": {
        "value": { "type": "array" }
      }
    }
  }
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • 1
    Great! In `is-array-condition`, does the `required` property add something? Isn't the `relation` property already required by the `condition` definition? – Joel Harris Apr 12 '18 at 15:30
  • 1
    tldr: It makes for better error messages. Longer answer: Without `required`, this condition can be true if the the relation is not present leading to requiring the type to be an array when it shouldn't. As you say, requiring it in the condition will cause the schema as a whole to fail whether it's present or not. So, keeping it in or not depends on whether or not you use the error messaging. I always include it any time I use the implication pattern. – Jason Desrosiers Apr 12 '18 at 16:04
8

If you are able to use the latest draft-7 version of JSON Schema, you can use if then else, as per https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-00#section-6.6

Although, using oneOf is also a valid approach, it might not be as clear to someone else inspecting your schema at a later date.

I've copied an example from an answer to another question:

If the "foo" property equals "bar", Then the "bar" property is required

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  },
  "if": {
    "properties": {
      "foo": { "enum": ["bar"] }
    }
  },
  "then": { "required": ["bar"] }
}

(You may want to check the draft support of the library you are using.)

Community
  • 1
  • 1
Relequestual
  • 11,631
  • 6
  • 47
  • 83
2

There may be a more concise way to do this, but this will work:

{
  "$schema": "http://json-schema.org/schema#",
  "title": "Rules",
  "description": "Describes a set of rules",
  "definitions": {
    "field": {
      "type": "string",
      "enum": ["Name", "Size"]
    }
  },
  "type": "object",
  "properties": {
    "rules": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "precedence": {
            "type": "number",
            "minimum": 0
          },
          "conditions": {
            "type": "array",
            "items": {
              "type": "object",
              "oneOf": [
                {
                  "properties": {
                    "field": {
                      "$ref": "#/definitions/field"
                    },
                    "relation": {
                      "type": "string",
                      "enum": ["is", "is not"]
                    },
                    "value": {
                      "type": ["string", "number"]
                    }
                  },
                  "required": ["field", "relation", "value"],
                  "additionalProperties": false
                },
                {
                  "properties": {
                    "field": {
                      "$ref": "#/definitions/field"
                    },
                    "relation": {
                      "type": "string",
                      "enum": ["is not one of", "is one of"]
                    },
                    "value": {
                      "type": ["array"]
                    }
                  },
                  "required": ["field", "relation", "value"],
                  "additionalProperties": false
                }
              ]
            }
          }
        },
        "required": ["precedence", "conditions"],
        "additionalProperties": false
      }
    }
  },
  "required": ["rules"],
  "additionalProperties": false
}
Joel Harris
  • 1,966
  • 3
  • 20
  • 32