object

Objects are the mapping type in JSON. They map "keys" to "values". In JSON, the "keys" must always be strings. Each of these pairs is conventionally referred to as a "property".

Language-specific info:
Python
Ruby

In Python, "objects" are analogous to the dict type. An important difference, however, is that while Python dictionaries may use anything hashable as a key, in JSON all the keys must be strings.

Try not to be confused by the two uses of the word "object" here: Python uses the word object to mean the generic base class for everything, whereas in JSON it is used only to mean a mapping from string keys to values.

schema
1
{ "type": "object" }
data
1
{
2
"key": "value",
3
"another_key": "another_value"
4
}
compliant to schema
data
1
{
2
"Sun": 1.9891e30,
3
"Jupiter": 1.8986e27,
4
"Saturn": 5.6846e26,
5
"Neptune": 10.243e25,
6
"Uranus": 8.6810e25,
7
"Earth": 5.9736e24,
8
"Venus": 4.8685e24,
9
"Mars": 6.4185e23,
10
"Mercury": 3.3022e23,
11
"Moon": 7.349e22,
12
"Pluto": 1.25e22
13
}
compliant to schema

Using non-strings as keys is invalid JSON:

data
1
{
2
0.01: "cm",
3
1: "m",
4
1000: "km"
5
}
not compliant to schema
data
1
"Not an object"
not compliant to schema
data
1
["An", "array", "not", "an", "object"]
not compliant to schema

Properties

The properties (key-value pairs) on an object are defined using the properties keyword. The value of properties is an object, where each key is the name of a property and each value is a schema used to validate that property. Any property that doesn't match any of the property names in the properties keyword is ignored by this keyword.

See Additional Properties and Unevaluated Properties for how to disallow properties that don't match any of the property names in properties.

For example, let's say we want to define a simple schema for an address made up of a number, street name and street type:

schema
1
{
2
"type": "object",
3
"properties": {
4
"number": { "type": "number" },
5
"street_name": { "type": "string" },
6
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
7
}
8
}
data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" }
compliant to schema

// If we provide the number in the wrong type, it is invalid:

data
1
{ "number": "1600", "street_name": "Pennsylvania", "street_type": "Avenue" }
not compliant to schema

By default, leaving out properties is valid. See Required Properties.

data
1
{ "number": 1600, "street_name": "Pennsylvania" }
compliant to schema

By extension, even an empty object is valid:

data
1
{ }
compliant to schema

By default, providing additional properties is valid:

data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
compliant to schema

Pattern Properties

Sometimes you want to say that, given a particular kind of property name, the value should match a particular schema. That's where patternProperties comes in: it maps regular expressions to schemas. If a property name matches the given regular expression, the property value must validate against the corresponding schema.

Regular expressions are not anchored. This means that when defining the regular expressions for patternProperties, it's important to note that the expression may match anywhere within the property name. For example, the regular expression "p" will match any property name with a p in it, such as "apple", not just a property whose name is simply "p". It's therefore usually less confusing to surround the regular expression in ^...$, for example, "^p$".

In this example, any properties whose names start with the prefix S_ must be strings, and any with the prefix I_ must be integers. Any properties that do not match either regular expression are ignored.

schema
1
{
2
"type": "object",
3
"patternProperties": {
4
"^S_": { "type": "string" },
5
"^I_": { "type": "integer" }
6
}
7
}
data
1
{ "S_25": "This is a string" }
compliant to schema
data
1
{ "I_0": 42 }
compliant to schema

If the name starts with S_, it must be a string

data
1
{ "S_0": 42 }
not compliant to schema

If the name starts with I_, it must be an integer

data
1
{ "I_42": "This is a string" }
not compliant to schema

This is a key that doesn't match any of the regular expressions:

data
1
{ "keyword": "value" }
compliant to schema

Additional Properties

The additionalProperties keyword is used to control the handling of extra stuff, that is, properties whose names are not listed in the properties keyword or match any of the regular expressions in the patternProperties keyword. By default any additional properties are allowed.

The value of the additionalProperties keyword is a schema that will be used to validate any properties in the instance that are not matched by properties or patternProperties. Setting the additionalProperties schema to false means no additional properties will be allowed.

Reusing the example from Properties, but this time setting additionalProperties to false.

schema
1
{
2
"type": "object",
3
"properties": {
4
"number": { "type": "number" },
5
"street_name": { "type": "string" },
6
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
7
},
8
"additionalProperties": false
9
}
data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" }
compliant to schema

Since additionalProperties is false, this extra property "direction" makes the object invalid:

data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
not compliant to schema

You can use non-boolean schemas to put more complex constraints on the additional properties of an instance. For example, one can allow additional properties, but only if their values are each a string:

schema
1
{
2
"type": "object",
3
"properties": {
4
"number": { "type": "number" },
5
"street_name": { "type": "string" },
6
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
7
},
8
"additionalProperties": { "type": "string" }
9
}
data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" }
compliant to schema

This is valid, since the additional property's value is a string:

data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
compliant to schema

This is invalid, since the additional property's value is not a string:

data
1
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "office_number": 201 }
not compliant to schema

You can use additionalProperties with a combination of properties and patternProperties. In the following example, based on the example from patternProperties, we add a "builtin" property, which must be a number, and declare that all additional properties (that are neither defined by properties nor matched by patternProperties) must be strings:

schema
1
{
2
"type": "object",
3
"properties": {
4
"builtin": { "type": "number" }
5
},
6
"patternProperties": {
7
"^S_": { "type": "string" },
8
"^I_": { "type": "integer" }
9
},
10
"additionalProperties": { "type": "string" }
11
}
data
1
{ "builtin": 42 }
compliant to schema

This is a key that doesn't match any of the regular expressions:

data
1
{ "keyword": "value" }
compliant to schema

It must be a string:

data
1
{ "keyword": 42 }
not compliant to schema

Extending Closed Schemas

It's important to note that additionalProperties only recognizes properties declared in the same subschema as itself. So, additionalProperties can restrict you from "extending" a schema using combining keywords such as allOf. In the following example, we can see how the additionalProperties can cause attempts to extend the address schema example to fail.

schema
1
{
2
"allOf": [
3
{
4
"type": "object",
5
"properties": {
6
"street_address": { "type": "string" },
7
"city": { "type": "string" },
8
"state": { "type": "string" }
9
},
10
"required": ["street_address", "city", "state"],
11
"additionalProperties": false
12
}
13
],
14

15
"properties": {
16
"type": { "enum": [ "residential", "business" ] }
17
},
18
"required": ["type"]
19
}

Fails additionalProperties. "type" is considered additional.

data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business"
6
}
not compliant to schema

Fails required. "type" is required.

data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC"
5
}
not compliant to schema

Because additionalProperties only recognizes properties declared in the same subschema, it considers anything other than "street_address", "city", and "state" to be additional. Combining the schemas with allOf doesn't change that. A workaround you can use is to move additionalProperties to the extending schema and redeclare the properties from the extended schema.

schema
1
{
2
"allOf": [
3
{
4
"type": "object",
5
"properties": {
6
"street_address": { "type": "string" },
7
"city": { "type": "string" },
8
"state": { "type": "string" }
9
},
10
"required": ["street_address", "city", "state"]
11
}
12
],
13

14
"properties": {
15
"street_address": true,
16
"city": true,
17
"state": true,
18
"type": { "enum": [ "residential", "business" ] }
19
},
20
"required": ["type"],
21
"additionalProperties": false
22
}
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business"
6
}
compliant to schema
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business",
6
"something that doesn't belong": "hi!"
7
}
not compliant to schema

Now the additionalProperties keyword is able to recognize all the necessary properties and the schema works as expected. Keep reading to see how the unevaluatedProperties keyword solves this problem without needing to redeclare properties.

::: {.index} single: object; properties; extending single: unevaluatedProperties :::

Unevaluated Properties

New in draft 2019-09

In the previous section we saw the challenges with using additionalProperties when "extending" a schema using combining. The unevaluatedProperties keyword is similar to additionalProperties except that it can recognize properties declared in subschemas. So, the example from the previous section can be rewritten without the need to redeclare properties.

schema
1
{
2
"allOf": [
3
{
4
"type": "object",
5
"properties": {
6
"street_address": { "type": "string" },
7
"city": { "type": "string" },
8
"state": { "type": "string" }
9
},
10
"required": ["street_address", "city", "state"]
11
}
12
],
13

14
"properties": {
15
"type": { "enum": ["residential", "business"] }
16
},
17
"required": ["type"],
18
"unevaluatedProperties": false
19
}
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business"
6
}
compliant to schema
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business",
6
"something that doesn't belong": "hi!"
7
}
not compliant to schema

unevaluatedProperties works by collecting any properties that are successfully validated when processing the schemas and using those as the allowed list of properties. This allows you to do more complex things like conditionally adding properties. The following example allows the "department" property only if the "type" of address is "business".

schema
1
{
2
"type": "object",
3
"properties": {
4
"street_address": { "type": "string" },
5
"city": { "type": "string" },
6
"state": { "type": "string" },
7
"type": { "enum": ["residential", "business"] }
8
},
9
"required": ["street_address", "city", "state", "type"],
10

11
"if": {
12
"type": "object",
13
"properties": {
14
"type": { "const": "business" }
15
},
16
"required": ["type"]
17
},
18
"then": {
19
"properties": {
20
"department": { "type": "string" }
21
}
22
},
23

24
"unevaluatedProperties": false
25
}
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "business",
6
"department": "HR"
7
}
compliant to schema
data
1
{
2
"street_address": "1600 Pennsylvania Avenue NW",
3
"city": "Washington",
4
"state": "DC",
5
"type": "residential",
6
"department": "HR"
7
}
not compliant to schema

In this schema, the properties declared in the then schema only count as "evaluated" properties if the "type" of the address is "business".

Required Properties

By default, the properties defined by the properties keyword are not required. However, one can provide a list of required properties using the required keyword.

The required keyword takes an array of zero or more strings. Each of these strings must be unique.

Draft-specific info
In Draft 4, required must contain at least one string.

In the following example schema defining a user record, we require that each user has a name and e-mail address, but we don't mind if they don't provide their address or telephone number:

schema
1
{
2
"type": "object",
3
"properties": {
4
"name": { "type": "string" },
5
"email": { "type": "string" },
6
"address": { "type": "string" },
7
"telephone": { "type": "string" }
8
},
9
"required": ["name", "email"]
10
}
data
1
{
2
"name": "William Shakespeare",
3
"email": "bill@stratford-upon-avon.co.uk"
4
}
compliant to schema

Providing extra properties is fine, even properties not defined in the schema:

data
1
{
2
"name": "William Shakespeare",
3
"email": "bill@stratford-upon-avon.co.uk",
4
"address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",
5
"authorship": "in question"
6
}
compliant to schema

Missing the required "email" property makes the JSON document invalid:

data
1
{
2
"name": "William Shakespeare",
3
"address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",
4
}
not compliant to schema

In JSON a property with value null is not equivalent to the property not being present. This fails because null is not of type "string", it's of type "null"

data
1
{
2
"name": "William Shakespeare",
3
"address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",
4
"email": null
5
}
not compliant to schema

Property names

New in draft 6

The names of properties can be validated against a schema, irrespective of their values. This can be useful if you don't want to enforce specific properties, but you want to make sure that the names of those properties follow a specific convention. You might, for example, want to enforce that all names are valid ASCII tokens so they can be used as attributes in a particular programming language.

schema
1
{
2
"type": "object",
3
"propertyNames": {
4
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
5
}
6
}
data
1
{
2
"_a_proper_token_001": "value"
3
}
compliant to schema
data
1
{
2
"001 invalid": "value"
3
}
not compliant to schema

Since object keys must always be strings anyway, it is implied that the schema given to propertyNames is always at least:

schema
1
{ "type": "string" }

Size

The number of properties on an object can be restricted using the minProperties and maxProperties keywords. Each of these must be a non-negative integer.

schema
1
{
2
"type": "object",
3
"minProperties": 2,
4
"maxProperties": 3
5
}
data
1
{}
not compliant to schema
data
1
{ "a": 0 }
not compliant to schema
data
1
{ "a": 0, "b": 1 }
compliant to schema
data
1
{ "a": 0, "b": 1, "c": 2 }
compliant to schema
data
1
{ "a": 0, "b": 1, "c": 2, "d": 3 }
not compliant to schema