Search code examples
jsonschemalinked-datajson-schema-validatorjson-schema-defaults

Importing all definitions from an external JSON Schema


I've been experimenting with JSON Pointers to reference and reuse JSON schemas.

Following the examples, I'm able to reference a specific property declared in another JSON schema and everything goes as expected, but I haven't found a way to extend a base JSON schema with the definitions of another base schema without having to explicitly reference every single property.

Seems like this would be something useful but I haven't found indication that it's possible or not.

Imagine the base schema things:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "http://example.com/thing.json",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "url": {
            "id": "url",
            "type": "string",
            "format": "uri"
        },
        "name": {
            "id": "name",
            "type": "string"
        }
    },
    "required": ["name"]
}

If I want a more specific person schema that reuses both properties of thing I could do:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "http://example.com/thing/person.json",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "url": {
            "$ref": "http://example.com/thing.json#/properties/url",
        },
        "name": {
            "$ref": "http://example.com/thing.json#/properties/name",
        },
        "gender": {
            "id": "gender",
            "type": "string",
            "enum": ["F", "M"]
        },
        "nationality": {
            "id": "nationality",
            "type": "string"
        },
        "birthDate": {
            "id": "birthDate",
            "type": "string",
            "format": "date-time"
        }
    },
    "required": ["gender"]
}

However there are two problems that I see with this approach:

  1. once a super-definition is updated, the dependent schemas also have to be updated
  2. it becomes cumbersome/verbose to manually maintain all these references
  3. rules (like required: name) are not part of the referenced definition

Is there a way to get the following effective JSON schema by using a single global reference?

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "http://example.com/thing/person.json",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "url": {
            "id": "url",
            "type": "string",
            "format": "uri"
        },
        "name": {
            "id": "name",
            "type": "string"
        }
        "gender": {
            "id": "gender",
            "type": "string",
            "enum": ["F", "M"]
        },
        "nationality": {
            "id": "nationality",
            "type": "string"
        },
        "birthDate": {
            "id": "birthDate",
            "type": "string",
            "format": "date-time"
        }
    },
    "required": ["name", "gender"]
}

I tried including $ref in the root of the schema, like so:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "http://jsonschema.net/thing/person",
    "type": "object",
    "additionalProperties": false,
    "$ref": "http://example.com/thing.json",
    "properties": {
        "gender": {/* ... */},
        "nationality": {/* ... */},
        "birthDate": {/* ... */}
    },
    "required": ["gender"]
}

This has the effect of inheriting thing properties but ignoring all the additional ones:

gender: Additional property gender is not allowed
nationality: Additional property nationality is not allowed
birthDate: Additional property birthDate is not allowed

Solution

  • You are looking for the allOf keyword. JSON Schema doesn't do inheritance like many of us are used to. Instead you can tell it that the data needs to be valid against both the parent schema (thing) and the child schema (person).

    {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "id": "http://example.com/thing.json",
        "type": "object",
        "properties": {
            "url": {
                "id": "url",
                "type": "string",
                "format": "uri"
            },
            "name": {
                "id": "name",
                "type": "string"
            }
        },
        "required": ["name"]
    }
    
    {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "id": "http://example.com/thing/person.json",
        "allOf": [
            { "$ref": "http://example.com/thing.json" },
            {
                "type": "object",
                "properties": {
                    "gender": {
                        "id": "gender",
                        "type": "string",
                       "enum": ["F", "M"]
                    },
                    "nationality": {
                        "id": "nationality",
                        "type": "string"
                    },
                    "birthDate": {
                        "id": "birthDate",
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "required": ["gender"]
            }
        ],
    }
    

    Or, as I prefer, written more consisely as

    {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "id": "http://example.com/thing/person.json",
        "allOf": [{ "$ref": "http://example.com/thing.json" }],
        "properties": {
            "gender": {
                "id": "gender",
                "type": "string",
                "enum": ["F", "M"]
            },
            "nationality": {
                "id": "nationality",
                "type": "string"
            },
            "birthDate": {
                "id": "birthDate",
                "type": "string",
                "format": "date-time"
            }
        },
        "required": ["gender"]
    }
    

    Notice that using this approach, you can not use "additionalProperties": false. It's for this reason that I always advise people that best practice is to ignore additional properties rather than explicitly forbidding them.