Skip to main content

A pythonic, extensible JSON Schema implementation.

Project description

jschon

Test Status Code Coverage Python Versions PyPI

Welcome to jschon, a JSON Schema implementation for Python!

Features

  • JSON Schema implementation, supporting specification drafts 2019-09 and 2020-12
  • Catalogue supporting custom metaschemas, vocabularies and format validators
  • JSON class implementing the JSON data model
  • RFC 6901 conformant JSON Pointer implementation
  • URI class (wraps rfc3986.URIReference)

Installation

pip install jschon

Usage

A quick demo

For a demonstration, let's implement this example, described in the JSON Schema core specification. We define a recursive tree structure, where each node in a tree can have a "data" field of any type. The first schema allows and ignores other instance properties. The second - an extension of the first - is more strict and only allows the "data" and "children" properties.

An example JSON instance with "data" misspelled as "daat" passes evaluation by the first schema, but fails against the second.

from jschon import JSON, JSONSchema
from jschon.catalogue import jsonschema_2020_12

# initialize the JSON Schema 2020-12 metaschema and vocabularies
jsonschema_2020_12.initialize()

# define an extensible tree schema
tree_schema = JSONSchema({
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/tree",
    "$dynamicAnchor": "node",
    "type": "object",
    "properties": {
        "data": True,
        "children": {
            "type": "array",
            "items": {
                "$dynamicRef": "#node"
            }
        }
    }
})

# define a strict-tree schema, which guards against misspelled properties
strict_tree_schema = JSONSchema({
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://example.com/strict-tree",
    "$dynamicAnchor": "node",
    "$ref": "tree",
    "unevaluatedProperties": False
})

# declare a JSON instance with a misspelled field
tree_instance = JSON({
    "children": [{"daat": 1}]
})

print(tree_schema.evaluate(tree_instance).valid)  # True
print(strict_tree_schema.evaluate(tree_instance).valid)  # False

JSON

The following code snippets may be copy-pasted into a Python console; expected values of expressions are shown in the adjacent comments. We begin by importing the JSON class:

from jschon import JSON

A JSON instance may be constructed from any JSON-compatible Python object. Let's take a look at a few simple examples, printing the JSON type of each:

JSON(None).type                           # 'null'
JSON(True).type                           # 'boolean'
JSON(3.14159).type                        # 'number'
JSON("Hello, World!").type                # 'string'
JSON((1, 2, 3)).type                      # 'array'
JSON({"foo": True, "bar": False}).type    # 'object'

Instances with the JSON types "array" and "object" are constructed recursively. Here we create an array and an object:

arr = JSON([1, 2, 3])
obj = JSON({"foo": True, "bar": False})

Nested JSON instances may be accessed using square-bracket notation:

arr[1]                                    # JSON(2)
obj["foo"]                                # JSON(True)

JSON implements the Sequence and Mapping interfaces for instances with the JSON types "array" and "object", respectively:

[item for item in arr]                    # [JSON(1), JSON(2), JSON(3)]
{key: val for key, val in obj.items()}    # {'foo': JSON(True), 'bar': JSON(False)}

JSON instances have several attributes, in addition to the type attribute seen above. These can be useful when working with complex JSON structures. Consider the following example:

document = JSON({
    "1a": {
        "2a": "foo",
        "2b": "bar"
    },
    "1b": [
        {"3a": "baz"},
        {"3b": "qux"}
    ]
})

A leaf node's value is the value from which it was constructed:

document["1a"]["2a"].value                # 'foo'

The parent attribute gives the containing instance:

document["1a"]["2b"].parent               # JSON({'2a': 'foo', '2b': 'bar'})

The path property returns a JSONPointer instance representing the path to the node from the document root:

document["1b"][0]["3a"].path              # JSONPointer('/1b/0/3a')

The key is the index of the node within its parent:

document["1b"][1]["3b"].key               # '3b'

Notice that, although an array item's sequential index is an integer, its key attribute is a string. This makes it interoperable with JSONPointer:

document["1b"][1].key                     # '1'

The value of an "object" node is a dict[str, JSON]:

document["1a"].value                      # {'2a': JSON('foo'), '2b': JSON('bar')}

The value of an "array" node is a list[JSON]:

document["1b"].value                      # [JSON({'3a': 'baz'}), JSON({'3b': 'qux'})]

Equality testing strictly follows the JSON data model. So, whereas the following two Python lists compare equal:

[False, True] == [0, 1]                   # True

The JSON equivalents are not equal, because the arrays' items have different JSON types:

JSON([False, True]) == JSON([0, 1])       # False

JSON also implements the <, <=, >=, > and != comparison operators, which may be used wherever it makes sense for the types of the given operands:

JSON(3) < JSON(3.01)                      # True

A JSON instance may be compared with any Python object. Internally, the non-JSON object is cast to its JSON equivalent before performing the comparison. Notice that tuples and lists are considered structurally equivalent:

(7, 11) == JSON([7, 11])                  # True

Jschon is not a JSON encoder/decoder. However, the JSON class supports both serialization and deserialization of JSON documents, via the Python standard library's json module.

Serializing a JSON instance is simply a matter of getting its string representation; for example:

str(JSON({'xyz': (None, False, True)}))   # '{"xyz": [null, false, true]}'

JSON instances can be deserialized from JSON files and JSON strings using the loadf and loads class methods, respectively:

JSON.loadf('/path/to/file.json')          # JSON(...)
JSON.loads('{"1": "spam", "2": "eggs"}')  # JSON({'1': 'spam', '2': 'eggs'})

Finally, a word on floating point numbers:

To ensure reliable operation of the JSON Schema "multipleOf" keyword, float values are converted to decimal.Decimal by the JSON constructor, and floats are parsed as decimal.Decimal during deserialization:

JSON(5.1).value                           # Decimal('5.1')

JSONPointer

JSONSchema

Contributing

See the guidelines for contributing.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jschon-0.2.0.tar.gz (31.8 kB view hashes)

Uploaded Source

Built Distribution

jschon-0.2.0-py3-none-any.whl (44.5 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page