diff --git a/source/basics.rst b/source/basics.rst index f130aef4..7c01fded 100644 --- a/source/basics.rst +++ b/source/basics.rst @@ -25,6 +25,28 @@ completely valid schema that will accept any valid JSON. -- { "an": [ "arbitrarily", "nested" ], "data": "structure" } +|draft6| + +You can also use ``true`` in place of the empty object to represent a schema +that matches anything, or ``false`` for a schema that matches nothing. + +.. schema_example:: + + true + -- + // This accepts anything, as long as it's valid JSON + 42 + -- + "I'm a string" + -- + { "an": [ "arbitrarily", "nested" ], "data": "structure" } + +.. schema_example:: + + false + --X + "Resistance is futile... This will always fail!!!" + The type keyword ---------------- @@ -77,10 +99,17 @@ more information. Declaring a unique identifier ----------------------------- -It is also best practice to include an ``id`` property as a unique +It is also best practice to include an ``$id`` property as a unique identifier for each schema. For now, just set it to a URL at a domain you control, for example:: - { "id": "http://yourdomain.com/schemas/myschema.json" } + { "$id": "http://yourdomain.com/schemas/myschema.json" } The details of `id` become more apparent when you start `structuring`. + +|draft6| + +.. draft_specific:: + + --Draft 4 + In Draft 4, ``$id`` is just ``id`` (without the dollar-sign). diff --git a/source/conf.py b/source/conf.py index c7c8468b..0e398358 100644 --- a/source/conf.py +++ b/source/conf.py @@ -20,7 +20,7 @@ sys.path.insert(0, os.path.abspath(os.path.dirname('__file__'))) # The standard of JSON Schema to test the examples against -jsonschema_standard = 4 +jsonschema_standard = 6 rst_prolog = """ .. role:: new @@ -55,7 +55,7 @@ # General information about the project. project = u'Understanding JSON Schema' -copyright = u'2013-{0}, Michael Droettboom, Space Telescope Science Institute'.format( +copyright = u'2013-2016 Michael Droettboom, Space Telescope Science Institute; © 2016-{0} Michael Droettboom'.format( datetime.datetime.now().year) # The version info for the project you're documenting, acts as replacement for @@ -63,9 +63,9 @@ # built documents. # # The short X.Y version. -version = '1.9alpha' +version = '6.0' # The full version, including alpha/beta/rc tags. -release = '1.9alpha' +release = '6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/source/conventions.rst b/source/conventions.rst index 51f6c79d..175b9a51 100644 --- a/source/conventions.rst +++ b/source/conventions.rst @@ -35,6 +35,27 @@ JSON in a few different languages: For C, you may want to consider using `Jansson `_ to read and write JSON. +Draft-specific notes +-------------------- + +The JSON Schema standard has been through a number of revisions or "drafts". The +most important are Draft 6, the most recent at the time of this writing, and +Draft 4, on which a lot of production software was built, and the draft for +which an earlier version of this book was written. + +The text is written to encourage the use of Draft 6 and gives +priority to the latest conventions and features, but where it differs from Draft +4, those differences are highlighted in special call-outs. If you only wish to +target Draft 6, you can safely ignore those sections. + +|draft6| + +.. draft_specific:: + + --Draft 4 + This is where anything pertaining to an old draft would be mentioned. + + Examples -------- diff --git a/source/index.rst b/source/index.rst index 0154936c..4d71f655 100644 --- a/source/index.rst +++ b/source/index.rst @@ -24,7 +24,7 @@ validator---just yet. .. note:: - This book describes JSON Schema draft 4. The most recent version is draft 7 + This book describes JSON Schema draft 6. The most recent version is draft 7 --- stay tuned, updates are coming! Earlier and later versions of JSON Schema are not completely compatible with the format described here. diff --git a/source/reference/array.rst b/source/reference/array.rst index 77304abb..46cf48aa 100644 --- a/source/reference/array.rst +++ b/source/reference/array.rst @@ -34,14 +34,15 @@ array may be of a different type. single: array; items single: items single: additionalItems + single: contains Items ''''' By default, the elements of the array may be anything at all. However, it's often useful to validate the items of the array against -some schema as well. This is done using the ``items`` and -``additionalItems`` keywords. +some schema as well. This is done using the ``items``, +``additionalItems``, and ``contains`` keywords. There are two ways in which arrays are generally used in JSON: @@ -91,6 +92,29 @@ number: // The empty array is always valid: [] +|draft6| + +While the ``items`` schema must be valid for **every** item in the array, the +``contains`` schema only needs to validate against one or more items in the +array. + +.. schema_example:: + + { + "type": "array", + "contains": { + "type": "number" + } + } + -- + // A single "number" is enough to make this pass: + ["life", "universe", "everything", 42] + --X + // But if we have no number, it fails: + ["life", "universe", "everything", "forty-two"] + -- + // All numbers is, of course, also okay: + [1, 2, 3, 4, 5] .. index:: single: array; tuple validation diff --git a/source/reference/generic.rst b/source/reference/generic.rst index 75dbc17f..ca7a63cd 100644 --- a/source/reference/generic.rst +++ b/source/reference/generic.rst @@ -8,21 +8,22 @@ for all JSON types. single: metadata single: title single: description + single: examples .. _metadata: Metadata -------- -JSON Schema includes a few keywords, ``title``, ``description`` and -``default``, that aren't strictly used for validation, but are used to -describe parts of a schema. +JSON Schema includes a few keywords, ``title``, ``description``, ``default``, and +``examples`` that aren't strictly used for validation, but are used to describe +parts of a schema. The ``title`` and ``description`` keywords must be strings. A "title" will preferably be short, whereas a "description" will provide a more lengthy explanation about the purpose of the data described by the schema. Neither are required, but they are encouraged for good -practice. +practice, and can make your schema "self-documenting". The ``default`` keyword specifies a default value for an item. JSON processing tools may use this information to provide a default value @@ -30,12 +31,23 @@ for a missing key/value pair, though many JSON schema validators simply ignore the ``default`` keyword. It should validate against the schema in which it resides, but that isn't required. +|draft6| The ``examples`` keyword is a place to provide an array of examples +that validate against the schema. This isn't used for validation, but may help +with explaining the effect and purpose of the schema to a reader. Each entry +should validate against the schema in which is resides, but that isn't strictly +required. There is no need to duplicate the ``default`` value in the +``examples`` array, since ``default`` will be treated as another example. + .. schema_example:: { "title" : "Match anything", "description" : "This is a schema that matches anything.", - "default" : "Default value" + "default" : "Default value", + "examples" : [ + "Anything", + 4035 + ] } .. index:: @@ -97,3 +109,38 @@ be valid against the enclosing schema: // This is in the ``enum``, but it's invalid against ``{ "type": // "string" }``, so it's ultimately invalid: null + +.. index:: + single: const + single: constant values + +.. _const: + +Constant values +--------------- + +|draft6| + +The ``const`` keyword is used to restrict a value to a single value. + +For example, to if you only support shipping to the United States for export reasons: + +.. schema_example:: + + { + "properties": { + "country": { + "const": "United States of America" + } + } + } + -- + { "country": "United States of America" } + --X + { "country": "Canada" } + +It should be noted that ``const`` is merely syntactic sugar for an ``enum`` with a single element, therefore the following are equivalent:: + + { "const": "United States of America" } + + { "enum": [ "United States of America" ] } diff --git a/source/reference/numeric.rst b/source/reference/numeric.rst index fd74e077..391f96b5 100644 --- a/source/reference/numeric.rst +++ b/source/reference/numeric.rst @@ -149,46 +149,78 @@ Range ''''' Ranges of numbers are specified using a combination of the -``minimum``, ``maximum``, ``exclusiveMinimum`` and -``exclusiveMaximum`` keywords. +``minimum`` and ``maximum`` keywords, (or ``exclusiveMinimum`` and +``exclusiveMaximum`` for expressing exclusive range). -- ``minimum`` specifies a minimum numeric value. +If *x* is the value being validated, the following must hold true: -- ``exclusiveMinimum`` is a boolean. When ``true``, it indicates that - the range excludes the minimum value, i.e., :math:`x > - \mathrm{min}`. When ``false`` (or not included), it indicates that - the range includes the minimum value, i.e., :math:`x \ge - \mathrm{min}`. + - *x* ≥ ``minimum`` + - *x* > ``exclusiveMinimum`` + - *x* ≤ ``maximum`` + - *x* < ``exclusiveMaximum`` -- ``maximum`` specifies a maximum numeric value. - -- ``exclusiveMaximum`` is a boolean. When ``true``, it indicates that - the range excludes the maximum value, i.e., :math:`x < - \mathrm{max}`. When ``false`` (or not included), it indicates that - the range includes the maximum value, i.e., :math:`x \le - \mathrm{max}`. +While you can specify both of ``minimum`` and ``exclusiveMinimum`` or both of +``maximum`` and ``exclusiveMinimum``, it doesn't really make sense to do so. .. schema_example:: { "type": "number", "minimum": 0, - "maximum": 100, - "exclusiveMaximum": true + "exclusiveMaximum": 100 } --X // Less than ``minimum``: -1 -- - // ``exclusiveMinimum`` was not specified, so 0 is included: + // ``minimum`` is inclusive, so 0 is valid: 0 -- 10 -- 99 --X - // ``exclusiveMaximum`` is ``true``, so 100 is not included: + // ``exclusiveMaximum`` is exclusive, so 100 is not valid: 100 --X // Greater than ``maximum``: 101 + +.. language_specific:: + + --Draft 4 + In JSON Schema Draft 4, ``exclusiveMinimum`` and ``exclusiveMaximum`` work + differently. There they are boolean values, that indicate whether + ``minimum`` and ``maximum`` are exclusive of the value. For example: + + - if ``exclusiveMinimum`` is ``false``, *x* ≥ ``minimum``. + - if ``exclusiveMinimum`` is ``true``, *x* > ``minimum``. + + This was changed to have better keyword independence. + + Here is an example using the older Draft 4 convention: + + .. schema_example:: 4 + + { + "type": "number", + "minimum": 0, + "maximum": 100, + "exclusiveMaximum": true + } + --X + // Less than ``minimum``: + -1 + -- + // ``exclusiveMinimum`` was not specified, so 0 is included: + 0 + -- + 10 + -- + 99 + --X + // ``exclusiveMaximum`` is ``true``, so 100 is not included: + 100 + --X + // Greater than ``maximum``: + 101 diff --git a/source/reference/object.rst b/source/reference/object.rst index a1bcefb9..a7d3ba05 100644 --- a/source/reference/object.rst +++ b/source/reference/object.rst @@ -193,9 +193,14 @@ 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 one or more strings. Each +The ``required`` keyword takes an array of zero or more strings. Each of these strings must be unique. +.. draft_specific:: + + --Draft 4 + 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: @@ -234,6 +239,43 @@ they don't provide their address or telephone number: "address": "Henley Street, Stratford-upon-Avon, Warwickshire, England", } +.. index:: + single: object; property names + single: propertyNames + +Property names +'''''''''''''' + +|draft6| + +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 a 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_example:: + + { + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + } + } + -- + { + "_a_proper_token_001": "value" + } + --X + { + "001 invalid": "value" + } + +Since object keys must always be strings anyway, so it is implied that the +schema given to ``propertyNames`` is always at least:: + + { "type": "string" } .. index:: single: object; size diff --git a/source/reference/schema.rst b/source/reference/schema.rst index fe3a73a4..9d002ee8 100644 --- a/source/reference/schema.rst +++ b/source/reference/schema.rst @@ -20,37 +20,14 @@ this at the root of your schema:: Advanced -------- -If you need to declare that your schema was written against a specific -version of the JSON Schema standard, and not just the latest version, -you can use one of these predefined values: - -- ``http://json-schema.org/schema#`` - - JSON Schema written against the current version of the - specification. - -- ``http://json-schema.org/hyper-schema#`` - - JSON Schema hyperschema written against the current version of the - specification. +If you need to declare that your schema was written against a specific version +of the JSON Schema standard, you should include the draft name in the path, for +example: +- ``http://json-schema.org/draft-06/schema#`` - ``http://json-schema.org/draft-04/schema#`` - JSON Schema written against this version. - -- ``http://json-schema.org/draft-04/hyper-schema#`` - - JSON Schema hyperschema written against this version. - -- ``http://json-schema.org/draft-03/schema#`` - - JSON Schema written against JSON Schema, draft v3 - -- ``http://json-schema.org/draft-03/hyper-schema#`` - - JSON Schema hyperschema written against JSON Schema, draft v3 - Additionally, if you have extended the JSON Schema language to include your own custom keywords for validating values, you can use a custom URI for ``$schema``. It must not be one of the predefined values -above. +above, and should probably include a domain name you own. diff --git a/source/reference/string.rst b/source/reference/string.rst index cfb62737..ecf4e8d1 100644 --- a/source/reference/string.rst +++ b/source/reference/string.rst @@ -129,6 +129,17 @@ exchanging the JSON documents also exchange information about the custom format types. A JSON Schema validator will ignore any format type that it does not understand. +.. index:: + single: date-time + single: email + single: hostname + single: ipv4 + single: ipv6 + single: uri + single: uri-reference + single: uri-template + single: json-pointer + Built-in formats ^^^^^^^^^^^^^^^^ @@ -153,3 +164,35 @@ specification. - ``"uri"``: A universal resource identifier (URI), according to `RFC3986 `_. + +- ``"uri-reference"``: |draft6| A URI Reference (either a URI or a + relative-reference), according to `RFC3986, section 4.1 + `_. + +- ``"json-pointer"``: |draft6| A JSON Pointer, according to `RFC6901 + `_. There is more discussion on the use + of JSON Pointer within JSON Schema in `structuring`. Note that this should be + used only when the entire string contains only JSON Pointer content, e.g. + ``/foo/bar``. JSON Pointer URI fragments, e.g. ``#/foo/bar/`` should use + ``"uri"`` or ``"uri-reference"``. + +- ``"uri-template"``: |draft6| A URI Template (of any level) according to + `RFC6570 `_. If you don't already know + what a URI Template is, you probably don't need this value. + +URI vs. URI reference +````````````````````` + +If the values in the schema the ability to be relative to a particular source +path (such as a link from a webpage), it is generally better practice to use +``"uri-reference"`` rather than ``"uri"``. ``"uri"`` should only be used when +the path must be absolute. + +.. draft_specific:: + + --Draft 4 + Draft 4 only includes ``"uri"``, not ``"uri-reference"``. Therefore, there is + some ambiguity around whether ``"uri"`` should accept relative paths. + + +.. TODO: Add some examples for ``format`` here diff --git a/source/structuring.rst b/source/structuring.rst index 330f14d9..54649667 100644 --- a/source/structuring.rst +++ b/source/structuring.rst @@ -22,16 +22,13 @@ For this example, let's say we want to define a customer record, where each customer may have both a shipping and a billing address. Addresses are always the same---they have a street address, city and state---so we don't want to duplicate that part of the schema -everywhere we want to store an address. Not only does it make the +everywhere we want to store an address. Not only would that make the schema more verbose, but it makes updating it in the future more -difficult. If our imaginary company were to start international +difficult. If our imaginary company were to start doing international business in the future and we wanted to add a country field to all the addresses, it would be better to do this in a single place rather than everywhere that addresses are used. -.. note:: - This is part of the draft 4 spec only, and does not exist in draft 3. - So let's start with the schema that defines an address:: { @@ -72,7 +69,12 @@ refer to the above, we would include:: { "$ref": "#/definitions/address" } -The value of ``$ref`` is a string in a format called `JSON Pointer +This can be used anywhere a schema is expected. You will always use ``$ref`` as +the only key in an object: any other keys you put there will be ignored by the +validator. + +The value of ``$ref`` is a URI, and the part after ``#`` sign (the +"fragment" or "named anchor") is in a format called `JSON Pointer `__. .. note:: @@ -80,9 +82,9 @@ The value of ``$ref`` is a string in a format called `JSON Pointer `_ from the XML world, but it is much simpler. -The pound symbol (``#``) refers to the current document, and then the -slash (``/``) separated keys thereafter just traverse the keys in the -objects in the document. Therefore, in our example +If you're using a definition from the same document, the ``$ref`` value begins +with the pound symbol (``#``). Following that, the slash-separated items traverse +the keys in the objects in the document. Therefore, in our example ``"#/definitions/address"`` means: 1) go to the root of the document @@ -104,7 +106,7 @@ schema for a customer: .. schema_example:: { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "definitions": { "address": { @@ -139,46 +141,193 @@ schema for a customer: } } +.. note:: + + Even though the value of a ``$ref`` is a URI, it is not a network locator, + only an identifier. This means that the schema doesn't need to be accessible + at that URI, but it may be. It is basically up to the validator + implementation how external schema URIs will be handled, but one should not + assume the validator will fetch network resources indicated in ``$ref`` + values. + +Recursion +````````` + +``$ref`` elements may be used to create recursive schemas that refer to themselves. +For example, you might have a ``person`` schema that has an array of ``children``, each of which are also ``person`` instances. + +.. schema_example:: + + { + "$schema": "http://json-schema.org/draft-06/schema#", + + "definitions": { + "person": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "children": { + "type": "array", + * "items": { "$ref": "#/definitions/person" }, + "default": [] + } + } + } + }, + + "type": "object", + + "properties": { + "person": { "$ref": "#/definitions/person" } + } + } + -- + // A snippet of the British royal family tree + { + "person": { + "name": "Elizabeth", + "children": [ + { + "name": "Charles", + "children": [ + { + "name": "William", + "children": [ + { "name": "George" }, + { "name": "Charlotte" } + ] + }, + { + "name": "Harry" + } + ] + } + ] + } + } + +Above, we created a schema that refers to another part of itself, effectively +creating a "loop" in the validator, which is both allowed and useful. Note, +however, that a loop of ``$ref`` schemas referring to one another could cause an +infinite loop in the resolver, and is explicitly disallowed. + +.. schema_example:: + + { + "definitions": { + "alice": { + "anyOf": [ + { "$ref": "#/definitions/bob" } + ] + }, + "bob": { + "anyOf": [ + { "$ref": "#/definitions/alice" } + ] + } + } + } + +.. index:: + single: $id + .. _id: -The id property ---------------- +The $id property +---------------- -The ``id`` property serves two purposes: +The ``$id`` property is a URI that serves two purposes: - It declares a unique identifier for the schema. -- It declares a base URL against which ``$ref`` URLs are resolved. +- It declares a base URI against which ``$ref`` URIs are resolved. -It is best practice that ``id`` is a URL, preferably in a domain that -you control. For example, if you own the ``foo.bar`` domain, and you -had a schema for addresses, you may set its ``id`` as follows:: +It is best practice that every top-level schema should set ``$id`` to an +absolute URI, with a domain that you control. For example, if you own the +``foo.bar`` domain, and you had a schema for addresses, you may set its ``$id`` +as follows: + +.. schema_example:: - "id": "http://foo.bar/schemas/address.json" + { "$id": "http://foo.bar/schemas/address.json" } This provides a unique identifier for the schema, as well as, in most cases, indicating where it may be downloaded. -But be aware of the second purpose of the ``id`` property: that it +But be aware of the second purpose of the ``$id`` property: that it declares a base URL for relative ``$ref`` URLs elsewhere in the file. -For example, if you had:: +For example, if you had: + +.. schema_example:: { "$ref": "person.json" } -in the same file, a JSON schema validation library would fetch -``person.json`` from ``http://foo.bar/schemas/person.json``, even if -``address.json`` was loaded from the local filesystem. +in the same file, a JSON schema validation library that supported network +fetching would fetch ``person.json`` from +``http://foo.bar/schemas/person.json``, even if ``address.json`` was loaded from +somewhere else, such as the local filesystem. + +|draft6| + +.. draft_specific:: + + --Draft 4 + In Draft 4, ``$id`` is just ``id`` (without the dollar sign). + +The ``$id`` property should never be the empty string or an empty fragment +(``#``), since that doesn't really make sense. + +Using $id with $ref +``````````````````` + +``$id`` also provides a way to refer to subschema without using JSON Pointer. +This means you can refer to them by a unique name, rather than by where they +appear in the JSON tree. + +Reusing the address example above, we can add an ``$id`` property to the +address schema, and refer to it by that instead. + +.. schema_example:: + + { + "$schema": "http://json-schema.org/draft-06/schema#", + + "definitions": { + "address": { + *"$id": "#address", + "type": "object", + "properties": { + "street_address": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" } + }, + "required": ["street_address", "city", "state"] + } + }, + + "type": "object", + + "properties": { + *"billing_address": { "$ref": "#address" }, + *"shipping_address": { "$ref": "#address" } + } + } + +.. note:: + + This functionality isn't currently supported by the Python ``jsonschema`` + library. Extending --------- -The power of ``$ref`` really shines when it is combined with the +The power of ``$ref`` really shines when it is used with the combining keywords ``allOf``, ``anyOf`` and ``oneOf`` (see :ref:`combining`). -Let's say that for shipping address, we want to know whether the +Let's say that for a shipping address, we want to know whether the address is a residential or business address, because the shipping -method used may depend on that. For the billing address, we don't +method used may depend on that. For a billing address, we don't want to store that information, because it's not applicable. To handle this, we'll update our definition of shipping address:: @@ -209,7 +358,7 @@ Tying this all together, .. schema_example:: { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "definitions": { "address": {