Objects

Here we show how to specify collections of key:value pairs using possibly nested JSON Schemas.

Object Schemas

Objects in JSON are collections of key:value pairs, where the values in these pairs can be again any JSON Document. JSON schemas that specify objects are called Object Schemas. The document

{"type": "object"}

is an Object Schema. It specifies any JSON document that is an object, such as

{
  "first_name": "Gary", 
  "last_name": "Medel"
}

or

{
  "name": {"first_name": "Gary", "last_name": "Medel"}, 
  "age": 27
}

but not, for instance

["this","is","an","array"]

Restrictions

JSON Schema contains several keywords allowing us to define more specific types of objects. For example, if we want to specify documents that include first_name, last_name and age, we can use

{
    "type": "object",
    "required": ["first_name", "last_name", "age"]
}

Object types that validate against this schema must have at least pairs with keys first_name, last_name and age.

However, we have not stated any restrictions for the values of these pairs. This means that the following document also validates against the previous schema:

{
    "first_name": 4,
    "last_name": true,
    "age": "whatever"
}

Not very informative, right? Ideally we would like the values of first_name and last_name to be strings and age to be an integer. We can achieve this as follows:

{
    "type": "object",
    "required": ["first_name", "last_name", "age"],
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"},
        "age": {"type": "integer"}
    }
}

This time, the only documents that validate against the schema are those that looke like the following one:

{
    "first_name": "Gary",
    "last_name": "Medel",
    "age": 27
}

Next we review all restrictions avaliable for Object Schemas.

Required

As we have mentioned, we use "required" to specify a list of strings that need to be present as key names in the list of key:value pairs that appear in a JSON document. In general, a schema of the form

{
    "type": "object",
    "required": ["a", "b", "c", "d"]
}

Specifies documents that need to have pairs with key names "a", "b", "c" and "d".

Properties

The property keyword is used to specify the key:value pairs of JSON documents. The value of property is itself a key:value pair, while the value can be any JSON schema and it is used to specify how the value of the key:value pair should look. For example, the following schema specifies that objects should have at least two pairs, with keys first_name and last_name, and the values of those must be strings.

{
    "type": "object",
    "required": ["first_name", "last_name"],
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"}
    }
}

Now let's say that we want to specify football players. They should have a name, age and a club_name. Furthermore, the name should consist of a first_name and a last_name. We achieve this using the following schema:

{
    "type": "object",
    "required": ["name", "age","club_name"],
    "properties": {
           "name": {
               "type": "object",
               "required": ["first_name", "last_name"],
               "properties": {
                       "first_name": {"type": "string"},
                       "last_name": {"type": "string"}
               }
           },
           "age": {"type": "integer"}, 
           "club_name": {"type": "string"}
    }
}

Note that the schema under "name" is again an Object Schema. We can do this as many times as we want! The following document validates against the schema above


{
   "name": {
      "first_name": "Gary",
      "last_name": "Medel"
   },
   "age": 27,
   "club_name": "Inter Milan"
}

By default the names specified under "properties" are not required, this means that, for example, the empty document

{}

validates against the schema

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"}
    }
}

For validation purproses the "properties" keyword should be understood as follows: if a document contains a key:value pair and the key of the pair is specified in "properties", then the value of the pair needs to validate against the schema specified under the key in "properties".

Additional Properties

The restriction "properties" does not state aything about the structure of any additional names not specified using this keyword. The "additionalProperties" keyword is used both to state if the document will support additional properties from the ones named in "properties" restriction and to give a schema for these extra properties. For example, let us analyze the following schema

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"}
    },
    "additionalProperties": false
}

In this case we are asking for documents that do not have any properties whose keywords are different from "first_name" and "last_name". For instance, this document would not validate:

{
    "first_name": "Gary",
    "last_name": "Medel",
    "age": 25
}

The other use of "additionalProperties" is to restrict the structure of the additional properties of the object. For example, consider the following schema

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"}
    },
    "additionalProperties": {
                             "type": "integer"
                            }
}

Here we are asking that any keywords different from "first_name" and "last_name" must have integer values. For example the next JSON document validates against the schema

{
    "first_name": "Gary",
    "last_name": "Medel",
    "age": 25
}

But this one does not

{
    "first_name": "Gary",
    "last_name": "Medel",
    "age": "twenty five"
}

Number of Properties

The "minProperties"and "maxProperties" keywords are used to restrict the number of properties allowed in a JSON object. For example, let us consider the following schema

{
    "type": "object",
    "minProperties": 3,
    "maxProperties": 5
}

Here we are asking for JSON objects with at least 3 properties and no more than 5. For example this object validates against the previous schema

{
    "first": 10,
    "second": 11,
    "third": 12,
    "four": 13
}

but this one does not

{
    "first": 10,
    "second": 11
}

Dependencies

Dependecies are used to modify the schema as a function of the key:value pairs that are present in the JSON document. There are two ways of doing this with the"dependencies" keyword.

The first way is to force the presence of some properties whenever a particular key is present in the document. Let us consider the following example

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"},
        "team": {"type": "string"},
        "league": {"type": "string"}
            },
    "required": ["first_name", "last_name"],
    "dependencies": {
                        "team": ["league"]    
                      }
}

Here we validate all documents that contain the keys "first_name" and "last_name", but if the object has the key "team" then the key "league" becomes a required property. For example, this document is valid against the schema

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter Milan",
    "league": "Serie A"
}

but this one is not valid

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter Milan"
}

The other way of using the "dependencies" keyword is to specify that the JSON object must also satisfy another JSON Schema whenever certain keys are present. Let us consider the following schema as an example

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name":  {"type": "string"},
        "team":  {"type": "string"}
                     },
    "required": ["first_name", "last_name"],
    "dependencies":{
                        "team": {
                        "type": "object",
                        "properties": {
                            "league": {"type": "string"},
                            "goals": {"type": "integer"}
                                         },
                       "required": ["league", "goals"]
                        }  
                      }
}

Now JSON objects that do have the key "team" must also validate against the schema


{
    "type": "object",
    "properties": {
        "league": {"type": "string"},
        "goals": {"type": "integer"}
        },
    "required": ["league", "goals"]
}

and thus must also have the keys "league" and "goals". Note that this is not a restriction on the value of the key team, but on the document itself. For example the next two objects validate against the complete schema

{
    "first_name": "Gary",
    "last_name": "Medel",
}
{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter Milan",
    "league": "Serie A",
    "goals": 5
}

But this one does not

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter Milan",
    "goals": 5
}

Nor does

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": {
            "league": "Serie A",
            "goals": 5
             }
}

Pattern Properties

Sometimes we want to restrict a set of properties depending if they share a pattern on their keys. The "patternProperties" keyword is used to restrict keywords matching a certain regular expression. Let us see an example of this

{
    "type": "object",
    "properties": {
        "first_name": {"type": "string"},
        "last_name": {"type": "string"},
        "team": {"type": "string"},
        "league": {"type": "string"}
            },
   "patternProperties": {
       "_goals$": { "type": "integer" }
  }
}

Here we are asking that keys matching the regular expression _goals$ must be integers. For example the following JSON object validates against the schema

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter de Milan",
    "league": "Serie A",
    "league_goals": 5,
    "international_goals": 2
}

but this one does not

{
    "first_name": "Gary",
    "last_name": "Medel",
    "team": "Inter Milan",
    "league": "Serie A",
    "league_goals": "five"
}

Formal Specification

The correct grammar for these schemmas can be seen like this:

 objSch:= "type": "object" (, objRes)*

Here objRes is a restriction for objects such that every objRes occurence in the schema is unique. Each of these restrictions is defined as follows:

objRes := prop | addprop | req | minprop | maxprop | dep | pattprop
prop := "properties": { kSch (, kSch)*}
kSch := kword: { JSch }
addprop := "additionalProperties": (bool | { JSch })
req := "required": [ kword (, kword)*]
minprop := "minProperties": n
maxprop := "maxProperties": n
dep := "dependencies": { kDep (, kDep)*}
kDep := (kArr | kSch)
kArr := kword: [ kword (, kword)*]
pattprop := "patternProperties": { patSch (, patSch)*}
patSch := "regExp": { JSch }

Here n is a natural number, bool is either true or false and regExp is a regular expression. Furthermore, kword is a JSON string and JSch a JSON Schema.

Formal Validation

Let O be an object Schema and J a JSON document. We say that a keyword k appears in J is J contains a key:value pair of the form k: j', for some document j'. Moreover, we use properties(O) to denote all keywords k1, ..., kn that appear in the key-value pair of the form "properties": {k1: s1 , ... , kn: sn} in O. The set properties(O) is empty if the keyword properties does not appear in O. Likewise, we use patternProperties(O) to denote all keywords k1, ..., kn that appear in the key-value pair of the form "additionalProperties": {k1: s1 , ... , kn: sn} in O. The set patternProperties(O) is empty if the keyword patternProperties does not appear in O.

We say that J validates against O if for each key:value pair k in O one of the following holds: