1
votes

I'm trying to validate JSON which is more complex than what's below, but hopefully this simpler example illustrates the issue cleanly.

Within the more complex schema, I need to have an array of objects, where:

  • All objects have a set of the same properties, like Name and Type
  • The Type property is an enum of allowed object types, such as "Full-Time", "Contractor"
  • Based on the value of the Type property, an additional per-type set of properties is defined. For example a "Full-Time" Employee has a Salary property which is required and an Email property which is optional, while a "Contractor" Employee has a Rate property which is required, and no optional additional properties.
  • Only properties which are defined should be allowed within each object
  • Required properties for each object may include those which are common across all objects, such as Name and Type, as well as those specific to one type of object, such as Salary

This seems like it should be a common problem, but so far I have not been able to find something which seems to match exactly what I'm trying to do. These came close, but I could not figure out exactly how to apply them to and/or which method is best for my use-case, so hoping someone who understands this area better can help:

In particular, it's not clear how the required and additionalProperties keywords should work across the common vs per-object properties.

Schema I have so far, with unwanted duplication:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "array",
  "items": {
    "$ref": "#/definitions/Employee"
  },
  "definitions": {
    "Employee": {
      "type": "object",
      "oneOf": [{
        "properties": {
          "Name": { "type": "string" },
          "Type": { "type": "string", "enum": [ "Full-Time" ] },
          "Salary": { "type": "integer" },
          "Email": { "type": "string" }
        },
        "required": [ "Name", "Type", "Salary" ],
        "additionalProperties": false
      },
      {
        "properties": {
          "Name": { "type": "string" },
          "Type": { "type": "string", "enum": [ "Contractor" ] },
          "Rate": { "type": "number" }
        },
        "required": [ "Name", "Type", "Rate" ],
        "additionalProperties": false
      }]
    }
  }
}

To validate this data:

[
  { "Name": "Good First Employee",    "Type": "Full-Time",  "Salary": 100000, "Email": "[email protected]"  },
  { "Name": "Good Second Employee",   "Type": "Full-Time",  "Salary":  90000 },
  { "Name": "Good First Contractor",  "Type": "Contractor", "Rate": 20.00 },
  { "Name": "Good Second Contractor", "Type": "Contractor", "Rate": 25 },
  { "Name": "Bad First Person",       "Type": "Unknown",    "Salary":  90000 },
  { "Name": "Bad Third Employee",     "Type": "Full-Time" },
  { "Name": "Bad Fourth Employee",    "Type": "Full-Time",  "Rate": 49.00 },
  { "Name": "Bad Fifth Employee",     "Type": "Full-Time",  "Salary": 100000, "Phone": "123-123-1234" },
  { "Name": "Bad Second Person" }
]

For the data above, objects with a Name starting with "Good" are valid. The rest starting with "Bad" are invalid:

  • Bad First Person - invalid Type
  • Bad Third Employee - missing Salary
  • Bad Fourth Employee - missing Salary, Rate invalid for Employee
  • Bad Fifth Employee - Phone invalid for Employee
  • Bad Second Person - missing Type (a common field)

Some of this works, but with excessive error messages that don't clearly indicate where the problem is.

1

1 Answers

0
votes

It IS a common problem. Sadly your unwanted duplication is the only way to make what you want happen for a draft-7 JSON Schema.

We (JSON Schema core team) did a lot of work on adding a new keyword for draft-8, but that's not published / done yet. https://github.com/json-schema-org/json-schema-spec/issues/556 - unevaluatedProperties.

The linked issue includes a lot of detail, including an example of where the OpenAPI specification has the same issue, and uses lots of deuplication as a result. The new key word cuts out lots of duplication.