Search code examples
jsonschemaajv

JSON Schema: Can I use "if" deeper than root of schema?


I want to validate objects like this

{
  type: "user",
  data: {id: 1},
}

and

{
  type: "account",
  data: {uuid: "xxxx"},
}

I thought I can write schema like this

{
  type: "object",
  properties: {
    type: {enum: ["user", "account"]},
    data: {
      "if": {properties: {type: {const: "user"}}},
      "then": {
        type: "object",
        properties: {
          id: {type: "number"}
        }
      },
      "else": {
        type: "object",
        properties: {
          uuid: {type: "string"}
        }
      },
    }
  }
}

but it looks like I can't refer to root type field from context of data field. So I have two questions. Can I use if deeper than root? If I can then how to refer to parent or root object from nested object? I'm using node ajv for validation.


Solution

  • You're correct, you cannot apply validation to a different part of your data than where your subschems is being applied. Let me explain a little.

    The first thing that happens when processing a JSON Schema is the Schema as a whole is "applied" to the instance as a whole.

    properties is an applicator keyword, in that it doesn't assert any validation rules by itself. The VALUES of a properties object are subschemas (which are Schemas in their own right) which are APPLIED to the instance location value when the associated KEY matches.

    To give you an exanple, in your Schema, the subschema at properties > type is applied to instance location type, which means the VALUE of the object key type. If type didn't exist in your data, the subschema in your Schema wouldn't do anything (it wouldn't be applied anywhere).

    This is broadly the processing model of a Schema against an instance. Hopefully this explains why you can't work in the way you're expecting. However it's still possible to achive what you want.

    then and else are conditional applicator keywords, and so need to be applied following the same model.

    What you need to do is use if, then, else at the top level, but have deep / nested application of the validation you want to do.

    Here's a demo with a new Schema and instance https://jsonschema.dev/s/sejHF

    {
      "$schema": "http://json-schema.org/draft-07/schema",
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "user",
            "account"
          ]
        },
        "data": {}
      },
      "if": {
        "properties": {
          "type": {
            "const": "user"
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "data": {
            "properties": {
              "id": {
                "type": "number"
              }
            }
          }
        }
      },
      "else": {
        "type": "object",
        "properties": {
          "data": {
            "properties": {
              "uuid": {
                "type": "string"
              }
            }
          }
        }
      }
    }
    

    If you also want to make sure that id is present when type is user, then you also need to add required: ['id'] in the then subschema. (You'll need to do similar in the else clause if you want to check for uuid too.