jsonvalidationjsonschemaajv

jsonSchema attribute conditionally required in array


I would like to define and validate such JSON schema with ajv :

    {
       "defaults": {
           "foo": {
               "bar": "Default value 1",
               "baz": "Default value 2"
           }
       },
      "entries": [
         {
            "foo": {
                "bar": "Overwritten value"
            },
            "other": 123
         }
      ]
    }

With the following restrictions :

I understand I probably have to use If-Then-Else structure, but it is unclear for me how to do it as user can provide as many "entries" wanted and examples I saw don't have this parent-child relationship.

Thanks in advance


Solution

  • The trick to describing this kind of constraint is that you have to write a bunch of nested schemas to describe the constraint from the top level. In JSON Schema, properties are always optional unless declared to be required. So, you don't need to write assertions for cases where things can be optional, just the ones where things are required.

    The following schema shows how to express the requirement: "If defaults.foo.bar is not present, then entries[x].foo.bar is mandatory". The "baz" requirement would follow the same pattern and the reset of the requirements either require no code or are trivial, so I expect you can figure out the rest.

    {
      ... describe the basic structure ...
    
      "allOf": [
        {
          "if": { "$ref": "#/$defs/defaults.foo.bar-is-not-present" },
          "then": { "$ref": "#/$defs/entries[x].foo.bar-is-required" }
        },
        {
          "if": { "$ref": "#/$defs/defaults.foo.baz-is-not-present" },
          "then": { "$ref": "#/$defs/entries[x].foo.baz-is-required" }
        }
      ],
    
      "$defs": {
        "defaults.foo.bar-is-not-present": {
          "type": "object",
          "properties": {
            "defaults": {
              "type": "object",
              "properties": {
                "foo": {
                  "not": { "required": ["bar"] }
                }
              },
              "required": ["foo"]
            }
          },
          "required": ["defaults"]
        },
    
        "entries[x].foo.bar-is-required": {
          "type": "object",
          "properties": {
            "entries": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "foo": { "required": ["bar"] }
                },
                "required": ["foo"]
              }
            }
          },
          "required": ["entries"]
        }
      },
    
      ... Filling in the missing definitions ...
    }