JSON

The JSON class is an implementation of the JSON data model. It is used to represent a JSON document that may be evaluated by a JSON schema. The JSONSchema class is itself a subclass of 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. We begin by importing the JSON class:

>>> from jschon import JSON
>>> JSON("Hello, World!").type
'string'
>>> JSON(3.14159).type
'number'
>>> JSON(None).type
'null'
>>> JSON(("a", "b", "c")).type
'array'

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": "quux"}
...     ]
... })

A leaf node’s data attribute holds the value from which it was constructed:

>>> document["1a"]["2a"].data
'foo'

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 parent attribute gives the containing instance:

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

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 is a string. This makes it interoperable with JSONPointer:

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

An "object" node’s data is a dict[str, JSON]:

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

An "array" node’s data is a list[JSON]:

>>> document["1b"].data
[JSON({'3a': 'baz'}), JSON({'3b': 'quux'})]

The value property returns the instance data as a JSON-compatible Python object:

>>> document["1b"].value
[{'3a': 'baz'}, {'3b': 'quux'}]

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

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

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

JSON also implements the <, <=, >= and > inequality operators, which may be used for numeric or string comparisons:

>>> JSON(3) < 3.01
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:

>>> 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'})