Basic features

If you have a few seconds, just read the next two sub-sections.

Setting and accessing value

You can use a Document class much like a normal python object. If you are using JSON via raw dictionaries/lists you’ll find that a lot of things are the same:

>>> from json_document.document import Document
>>> doc = Document({})
>>> doc['hello'] = 'world'

The first major difference is evident when you want to refer to data. Instead of returning the value directly you get an instance of DocumentFragment. To access the value you’ll have to use the value property:

>>> doc['hello'].value
'world'
>>> doc.value
{'hello': 'world'}

Accessing the fragment directly is also possible and indeed is where a lot of the value json_document brings is hidden. To learn about that though you’ll have to learn about using schema with your documents.

Using document schema

Schema defines how a valid document looks like. It is constructed of a series of descriptions (written in JSON itself) and is quite powerful in what can be expressed. If you have no experience with JSON-Schema you can think of it as DTD for XML on steroids. Don’t worry it’s easy to learn by example.

Let’s design a simple schema for a personnel registry. Each document will describe one person and will remember their name and age:

>>> person_schema = {
...     "type": "object",
...     "title": "Person record",
...     "description": "Simplified description of a person",
...     "properties": {
...         "name": {
...             "type": "string",
...             "title": "Full name"
...         },
...         "age": {
...             "type": "number",
...             "title": "Age in years"
...         }
...     }
... }

This schema can be read as follows:

  • The root element is an object titled “Person record”.
  • It has a property “name” that is a string titled “Full name”.
  • It also has a property “age” that is a number titled “Age in years”

This schema is very simple but it already defines the correct shape of a document. It defines the type of the root object (a json “object”, for python that translates to a dictionary instance). It also describes the attributes of that object (name and age) and their type. The schema also mixes documentation elements via the “title” and “description” properties.

Using a schema you can validate documents for correctness. Let’s see how that works:

>>> joe = Document({"name": "joe", "age": 32}, person_schema)
>>> joe.validate()

Calling validate() would have raised an exception if joe was not a valid “Person record”. Let’s set the age to an invalid type to see how that works:

>>> joe["age"] = "thirty two"
>>> joe.validate()
Traceback (most recent call last):
...
ValidationError: ValidationError: Object has incorrect type (expected number) object_expr='object.age', schema_expr='schema.properties.age.type')

Boom! Not only did the validation fail. We’ve got a detailed error message that outlines the problem. It also gives us the JavaScript expression that describes the part that did not match the schema (object.age) and the part of the schema that was violated (schema.properties.age.type).

Because the actual value is hidden behind the .value property we can stash a set of useful properties and methods in each DocumentFragment. One of them is .schema which unsurprisingly returns the associated schema element (if we have one). Instead of returning the raw JSON schema it returns a smart wrapper around it that has properties corresponding to each legal schema part (such as .type and .properties). You can use it to access meta-data such as title and description:

>>> joe["age"].schema.title
'Age in years'
>>> joe.schema.description
'Simplified description of a person'

You can also access things like type but be aware that it has some quirks. Refer to json-schema-validator documentation for details on the Schema class. For example, the type is automatically converted to a list of valid types:

>>> joe["name"].schema.type
['string']

One useful property is .schema.optional which tells if if an element is required or not. By default everything is required, unless marked optional:

>>> joe["name"].schema.optional
False