1

I am trying to model a json-schema that supports arbitrary boolean expressions like:

(prop1 > 'val1' || prop2 = 'val2') && !(prop3 in [1, 2, 3])
{
    and: [
        {
            or: [
                { property: 'prop1', operator: 'gt', value: 'val1' },
                { property: 'prop2', operator: 'eq', value: 'val2' }
            ]
        },
        {
            not: { property: 'prop3', operator: 'in', value: [1, 2, 3] }
        }
    ]
}

This is the schema so far:

{
  "$ref": "#/definitions/BooleanExpression",
  "definitions": {
    "BooleanExpression": {
      "oneOf": [
        { "$ref": "#/definitions/BooleanCondition" },
        { "$ref": "#/definitions/BooleanAnd" },
        { "$ref": "#/definitions/BooleanOr" },
        { "$ref": "#/definitions/BooleanNot" }
      ]
    },

    "BooleanOperator": {
      "type": "string",
      "enum": [
        "eq",
        "gt",
        "gte",
        "lt",
        "lte",
        "in"
      ]
    },

    "BooleanCondition": {
      "type": "object",
      "required": [
        "property",
        "operator",
        "value"
      ],
      "properties": {
        "property": {
          "type": "string",
          "minLength": 1
        },
        "operator": {
          "$ref": "#/definitions/BooleanOperator"
        },
        "value": {
          "type": [
            "string",
            "number",
            "boolean",
            "null",
            "array"
          ]
        }
      }
    },

    "BooleanAnd": {
      "type": "object",
      "required": [ "and" ],
      "additionalProperties": false,
      "properties": {
        "and": {
          "type": "array",
          "minItems": 2,
          "items": {
            "$ref": "#/definitions/BooleanExpression"
          }
        }
      }
    },

    "BooleanOr": {
      "type": "object",
      "required": [ "or" ],
      "additionalProperties": false,
      "properties": {
        "or": {
          "type": "array",
          "minItems": 2,
          "items": {
            "$ref": "#/definitions/BooleanExpression"
          }
        }
      }
    },

    "BooleanNot": {
      "type": "object",
      "required": [ "not" ],
      "additionalProperties": false,
      "properties": {
        "not": {
          "$ref": "#/definitions/BooleanExpression"
        }
      }
    }
  }
}

I would like to express constraints reguarding allowed types for the $.value field in BooleanCondition based on the value of $.operator field. For example, if $.operator = in, $.value should be only an array of integers or strings:

{
    property: 'prop1',
    operator: 'in',
    value: 'should not compile'
}

{
    property: 'prop1',
    operator: 'in',
    value: ['this', 'is', 'ok']
}

Or if $.operator = eq, then $.value can be of type string, number, boolean or null (but not array).

{
    property: 'prop1',
    operator: 'eq',
    value: ['should', 'not', 'compile']
}

{
    property: 'prop1',
    operator: 'eq',
    value: 'this is ok'
}

Is it possible to express these types of conditional constraints in the schema?

revy
  • 3,945
  • 7
  • 40
  • 85
  • 1
    Related question: [how to set the type of a schema object based on the value of another property?](https://stackoverflow.com/questions/53881623/how-to-set-the-type-of-a-schema-object-based-on-the-value-of-another-property) – Yogi Oct 09 '22 at 17:26
  • 1
    @Yogi thanks! I was able to arrange a solution based on that – revy Oct 09 '22 at 20:48
  • And thanks for posting your solution. Writing good schemas can be very frustrating and having examples here definitely helps others. – Yogi Oct 09 '22 at 22:00

1 Answers1

1

As described in this other question, this is a possible solution using if then else statements:

{
  "$ref": "#/definitions/BooleanExpression",
  "definitions": {
    "BooleanExpression": {
      "oneOf": [
        { "$ref": "#/definitions/BooleanCondition" },
        { "$ref": "#/definitions/BooleanAnd" },
        { "$ref": "#/definitions/BooleanOr" },
        { "$ref": "#/definitions/BooleanNot" }
      ]
    },

    "BooleanOperator": {
      "type": "string",
      "enum": [
        "eq",
        "gt",
        "gte",
        "lt",
        "lte",
        "in"
      ]
    },

    "BooleanCondition": {
      "type": "object",
      "required": [
        "property",
        "operator",
        "value"
      ],
      "properties": {
        "property": {
          "type": "string",
          "minLength": 1
        },
        "operator": {
          "$ref": "#/definitions/BooleanOperator"
        },
        "value": {
          "type": [
            "string",
            "number",
            "boolean",
            "null",
            "array"
          ]
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "operator": {
                "const": "in"
              }
            }
          },
          "then": {
            "properties": {
              "value": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": [
                    "string",
                    "number"
                  ]
                }
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "operator": {
                "enum": [
                  "gt",
                  "gte",
                  "lt",
                  "lte"
                ]
              }
            }
          },
          "then": {
            "properties": {
              "value": {
                "type": [
                  "string",
                  "number"
                ]
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "operator": {
                "const": "eq"
              }
            }
          },
          "then": {
            "properties": {
              "value": {
                "type": [
                  "string",
                  "number",
                  "boolean",
                  "null"
                ]
              }
            }
          }
        }
      ]
    },

    "BooleanAnd": {
      "type": "object",
      "required": [ "and" ],
      "additionalProperties": false,
      "properties": {
        "and": {
          "type": "array",
          "minItems": 2,
          "items": {
            "$ref": "#/definitions/BooleanExpression"
          }
        }
      }
    },

    "BooleanOr": {
      "type": "object",
      "required": [ "or" ],
      "additionalProperties": false,
      "properties": {
        "or": {
          "type": "array",
          "minItems": 2,
          "items": {
            "$ref": "#/definitions/BooleanExpression"
          }
        }
      }
    },

    "BooleanNot": {
      "type": "object",
      "required": [ "not" ],
      "additionalProperties": false,
      "properties": {
        "not": {
          "$ref": "#/definitions/BooleanExpression"
        }
      }
    }
  }
}
revy
  • 3,945
  • 7
  • 40
  • 85