Search code examples
jsonrecursionjsonschema

Recursive JSON Schema with cascading required fields


I would like to create a recursive schema, where I allow for arbitrary keys inside a nested structure. Basic schema is this:

{
  "type": "object",
  "properties": {
    "Age": {
      "type": "number"
    },
    "Name": {
      "type": "string"
    }
  },
  "patternProperties": {
    ".*": {
      "$ref": "#"
    }
  },
  "additionalProperties": false
}

Now I can insert arbitrary keys mapping to a value, which adheres to the same schema again. Now the problem: I need to enforce that both Age and Name are present in every deepest object, by either being directly contained, or being contained in a parent. Valids:

{
  "level1": {
    "level2": {
      "Age": 20,
      "Name": "test"
    }
  }
}
{
  "level1": {
    "level2": {
      
    },
    "Age": 20
  },
  "Name": "test"
}
{
  "level1": {
    "level2": {
      "Age": 20
    }
  },
  "Name": "test"
}
{
  "level1": {
    "level2": {
      "Age": 20
    }
  },
  "level3": {
    "Age": 10
  },
  "Name": "test"
}

Invalid

{
  "level1": {
    "level2": {
      "Age": 20
    }
  },
  "level3": {
    "Name": "test2"
  },
  "Name": "test"
}

Because the level3 object has no "Age" in itself or in a parent.

How to check for "If I am the deepest object, is somefield contained in me or in any of my ancestors"?

Short background: The json should be used as a configuration file, where multiple concrete configurations can be inserted. I would like the user to be able to simply insert common parameters into higher layers from which they will be cascaded into the concrete configurations.


Solution

  • The problem is that your patternProperties .* is also catching Age and Name. In order to continue using patternProperties, you'll need to exclude those from the regex.

    Better yet, just drop the patternProperties and put your $ref subschema in additionalProperties, which accepts schemas.

    {
      "type": "object",
      "properties": {
        "Age": {
          "type": "number"
        },
        "Name": {
          "type": "string"
        }
      },
      "additionalProperties": {
        "$ref": "#"
      }
    }
    

    How to check for "If I am the deepest object, is somefield contained in me or in any of my ancestors"?

    I don't think this is possible with JSON Schema.

    Each subschema can only look at the current insurance location, not ancestors. You'd need figure out a way to check at the parent level to see if a child contains Age. I'm not sure how to do that either.