Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of problems with "inheritance" and additionalProperties: false #661

Closed
bquinn opened this issue Oct 3, 2018 · 2 comments
Closed

Comments

@bquinn
Copy link

bquinn commented Oct 3, 2018

We are coming up against issues with additionalProperties="false" (as documented very well in other issues) and in conversation on the json-schema slack workspace @Relequestual suggested that we provide examples here so we could check whether the changes coming in draft-08 (#556) would solve our problems.

We're from IPTC, an international standards body for the media industry, trying to make a JSON version of our existing XML-based standard SportsML. Ideally the JSON version will be semantically compatible with the XML version.

The XML Schema version uses a lot of inheritance to cover properties that are common to many elements in sports data: people, places, statistics, venues etc.

To go back to the simplest example, and avoiding sports for now, take the following schema, simple-schema.json. We have a teacher and a student object, both of which inherit shared properties from personProperties. We include the shared properties using the allOf construct.

{
    "$id": "simple-schema.json#",
    "$schema": "http://json-schema.org/draft-06/schema#",
    "description": "Really simple schema",
    "additionalProperties": false,
    "type": "object",
    "properties": {
        "student": {
            "$ref": "#/definitions/student"
        },
        "teacher": {
            "$ref": "#/definitions/teacher"
        }
    },
    "definitions": {
        "personProperties": {
            "description": "Properties shared by all people.",
            "properties": {
                "date-of-birth": {
                    "type": "string",
                    "description": "Date of the person's birth."
                },
                "name": {
                    "type": "string",
                    "description": "The person's name."
                }
            }
        },
        "student": {
            "type": "object",
            "allOf": [{
                "$ref": "#/definitions/personProperties"
            }],
            "properties": {
                "graduation-year": {
                    "type": "string",
                    "description": "Year in which the student is expected to graduate."
                }
            }
        },
        "teacher": {
            "type": "object",
            "allOf": [{
                "$ref": "#/definitions/personProperties"
            }],
            "properties": {
                "specialist-subject": {
                    "type": "string",
                    "description": "Subject that this teacher specialises in."
                }
            }
        }
    }
}

Then we have the following instances, example1-valid.json:

{
    "student": {
        "name": "Susan Smith",
        "date-of-birth": "2010-01-01",
        "graduation-year": "2028"
    }
}

And example2-invalid.json:

{
    "teacher": {
        "name": "Joseph Khan",
        "date-of-birth": "1990-08-08",
        "graduation-year": "2020"
    }
}

When we try to validate these, we want example1 to pass and example2 to fail, because it’s introducing an element that shouldn’t be part of the teacher definition.

But they both pass:

$ jsonschema -i example1-valid.json simple-schema.json 
$ jsonschema -i example2-invalid.json simple-schema.json 
$

If we add additionalProperties="false" to the definitions of student and teacher, they both fail:

$ jsonschema -i example1-valid.json simple-schema-no-additional-preferences.json 
{'name': 'Susan Smith', 'date-of-birth': '2010-01-01', 'graduation-year': '2028'}: Additional properties are not allowed ('name', 'date-of-birth' were unexpected)
$ jsonschema -i example2-invalid.json simple-schema-no-additional-preferences.json 
{'name': 'Joseph Khan', 'date-of-birth': '1990-08-08', 'graduation-year': '2020'}: Additional properties are not allowed ('graduation-year', 'date-of-birth', 'name' were unexpected)
$ 

The only way that we can get the functionality that we want is if we “unroll” the “inherited” objects and repeat the definitions of date-of-birth and name in both student and teacher. Then we can declare additionalProperties="false" and it works:

{
    "$id": "simple-schema-unrolled.json#",
    "$schema": "http://json-schema.org/draft-06/schema#",
    "description": "Really simple schema with unrolled inherited properties",
    "additionalProperties": false,
    "type": "object",
    "properties": {
        "student": {
            "$ref": "#/definitions/student"
        },
        "teacher": {
            "$ref": "#/definitions/teacher"
        }
    },
    "definitions": {
        "student": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "date-of-birth": {
                    "type": "string",
                    "description": "Date of the person's birth."
                },
                "name": {
                    "type": "string",
                    "description": "The person's name."
                },
                 "graduation-year": {
                    "type": "string",
                    "description": "Year in which the student is expected to graduate."
                }
            }
        },
        "teacher": {
            "additionalProperties": false,
            "type": "object",
            "properties": {
                "date-of-birth": {
                    "type": "string",
                    "description": "Date of the person's birth."
                },
                "name": {
                    "type": "string",
                    "description": "The person's name."
                },
                 "specialist-subject": {
                    "type": "string",
                    "description": "Subject that this teacher specialises in."
                }
            }
        }
    }
}

Then our validation works properly:

$ jsonschema -i example1-valid.json simple-schema-unrolled.json 
$ jsonschema -i example2-invalid.json simple-schema-unrolled.json 
{'name': 'Joseph Khan', 'date-of-birth': '1990-08-08', 'graduation-year': '2020'}: Additional properties are not allowed ('graduation-year' was unexpected)
$

But this workaround requires huge amounts of duplication in our complex schema and makes it very difficult to maintain. We would be able to generate the “unrolled” schema automatically but we feel that JSON Schema should give us the ability to validate “inherited” properties out of the box.

@Relequestual
Copy link
Member

Yup I'm positive that unevaluatedProperties would solve your issue here!

I've seen what you want achived in a slightly different way, allowing you to still keep the modulairty you want.

{
  "$id": "simple-schema.json#",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "description": "Really simple schema",
  "additionalProperties": false,
  "type": "object",
  "properties": {
    "student": {
      "allOf": [
        {
          "$ref": "#/definitions/student"
        },{
          "not": {
            "$ref": "#/definitions/teacher"
          }
        }
      ]
    },
    "teacher": {
      "allOf": [
        {
          "$ref": "#/definitions/teacher"
        },{
          "not": {
            "$ref": "#/definitions/student"
          }
        }
      ]
    }
  },
  "definitions": {
    "personProperties": {
      "description": "Properties shared by all people.",
      "properties": {
        "date-of-birth": {
          "type": "string",
          "description": "Date of the person's birth."
        },
        "name": {
          "type": "string",
          "description": "The person's name."
        }
      }
    },
    "student": {
      "type": "object",
      "allOf": [
        {
          "$ref": "#/definitions/personProperties"
        }
      ],
      "properties": {
        "graduation-year": {
          "type": "string",
          "description": "Year in which the student is expected to graduate."
        }
      },
      "required": ["raduation-year"]
    },
    "teacher": {
      "type": "object",
      "allOf": [
        {
          "$ref": "#/definitions/personProperties"
        }
      ],
      "properties": {
        "specialist-subject": {
          "type": "string",
          "description": "Subject that this teacher specialises in."
        }
      },
      "required": ["specialist-subject"]
    }
  }
}

This passes your validation, but would not scale wth N types you want to check.

With draft-8, your student definition would look like this:

"student": {
      "type": "object",
      "$ref": "#/definitions/personProperties",
      "properties": {
        "graduation-year": {
          "type": "string",
          "description": "Year in which the student is expected to graduate."
        }
      },
      "required": ["raduation-year"],
      "unevaluatedProperties": false
    }

unevaluatedProperties is like additionalProperties except it can "see through" references and applicator key words (like anyOf).


This is probably a secondary thing, but I would define teacher and student like the following...

"teacher": {
  "allOf": [{
    "$ref": "#/definitions/personProperties"
    },{
    "type": "object",
    "properties": {
      "specialist-subject": {
        "type": "string",
        "description": "Subject that this teacher specialises in."
      }
    }
  }],
}

@handrews
Copy link
Contributor

Closing based on original poster's thumbs-up on the response. Please re-open if there was more to do here. unevaluatedProperties is already tracked in PR #656

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants