How to extend a schema in JSON schema?

Panagiotis Panagi picture Panagiotis Panagi · Sep 29, 2018 · Viewed 8.3k times · Source

I'm using JSON schema for data modelling. I define a base Document schema, that I later use to define model schemas (e.g. Product, Category, User, etc.).

I'm doing this because I want all models to inherit certain structure/rules. For example every model instance should have certain common properties (such as, id, createdAt, updatedAt). In OOP terminology: Product extends Document and therefore it inherits its instance properties. In schemas terminology (I think) Document is a meta-schema for creating model schemas.

I've defined the Document schema as follows:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "id": "http://example.com/schemas/document.json#",
  "title": "Document",
  "type": "object",
  "additionalProperties": false,
  "required": ["type", "name", "fields"],
  "properties": {
    "type": {
      "constant": "document"
    },
    "name": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "readOnly": {
      "type": "boolean"
    },
    "properties": {
      // common properties 
      // model-specific properties
    }
  }
}
  1. How do I specify that the Document meta-schema "extends" the base JSON schema (draft-07), so that I don't have to define all the properties of the draft ($schema, id, etc.)?
  2. How do I specify that the properties of each model schema contains some common properties (id, createdAt, ...), without having to define them in each model schema definition?

Answer

Jason Desrosiers picture Jason Desrosiers · Sep 30, 2018

JSON Schema doesn't use an object oriented paradigm, so concepts like inheritance don't translate well. JSON Schema is a collection of constraints. It's subtractive rather than additive like most people are used to. This means that given an empty schema, the set of valid JSON documents is the set of all JSON documents. As you add keywords, you are subtracting from the set of valid JSON documents. Once something is removed from the set, it can't be added back in.

Therefore, you can use composition to "extend" a schema, but you can never "override" something that another schema defines.

/schema/base

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" }
    "bar": { "type": "string" }
  }
}

/schema/extended

{
  "allOf": [{ "$ref": "/schema/base" }],
  "properties": {
    "baz": { "type": "string" }
  }
}

Here we have a simple extension that works great with JSON Schema.

/schema/override

{
  "allOf": [{ "$ref": "/schema/base" }],
  "properties": {
    "bar": { "type": "integer" },
    "baz": { "type": "boolean" }
  }
}

In this example, both schemas have a /properties/bar field. If you are thinking about this in terms of inheritance, you're going to misunderstand what is happening here. In this case, both "/properties/bar" fields must be valid. There is no conflict to resolve. As the keyword says, "all of" the schemas must be valid.

Hopefully that gives you enough information to solve your problem and avoid the the most common gotcha.