Extending json-five

The json way

json5.load and json5.loads support a similar interface to the stdlib json module. Specifically, you can provide the following arguments that have the same meaning as in json.load:

  • parse_int

  • parse_float

  • parse_constant

  • object_hook

  • object_pairs_hook

This is convenient if you have existing code that uses these arguments with the json module, but want to also support JSON5. These options are also useful as a simple way to customize parsing of json types.

Additionally, a new hook keyword argument, parse_json5_identifiers, is available to help users control the output of parsing identifiers. By default, JSON5 Identifiers in object keys are returned as a JsonIdentifier object, which is a subclass of str (meaning it’s compatible anywhere str is accepted). This helps keep keys the same round-trip, rather than converting unquoted identifiers into quoted strings, such that dumps(loads(text)) == text (in this case).

You can change this behavior with the parse_json5_identifiers keyword argument with a callable that receives the JsonIdentifier object and its return value is used instead. For example, you can specify parse_json5_identifiers=str to convert identifiers to normal strings, such that dumps(loads('{foo: "bar"}')) == '{"foo": "bar"}'.

However, this package does not support the cls keyword found in the standard library json module. If you want to implement custom serializers/deserializers, read on about custom loaders/dumpers.

Custom Loaders and Dumpers

This package uses “Loaders” as part of the deserialization of JSON text to Python. “Dumpers” are used to serialize Python objects to JSON text.

The entry points for loaders and dumpers are the load and dump methods, respectively. You can override these methods to implement custom loading of models or dumping of objects.

Extending the default loader

The default loader takes in a model and produces, in the default case, Python objects.

As a simple example, you can extend the default loader with your own to customize loading of lists. Here, I’ll create a custom loader that, when it encounters an array (json5.model.JSONArray) with with only one value, it will return the single value, rather than a single-item array.

from json5.loader import DefaultLoader, loads
from json5.model import JSONArray


class MyCustomLoader(DefaultLoader):
    def load(self, node):
        if isinstance(node, JSONArray):
            return self.json_array_to_python(node)
        else:
            return super().load(node)

    def json_array_to_python(self, node):
        if len(node.values) == 1:
            return self.load(node.values[0])
        else:
            return super().json_array_to_python(node)

The loads function accepts a loader keyword argument, where the custom loader can be passed in.

json_string = "{foo: ['bar', 'baz'], bacon: ['eggs']}"
loads(json_string)  # Using the regular default loader
# {'foo': ['bar', 'baz'], 'bacon': ['eggs']}

loads(json_string, loader=MyCustomLoader())  # use the custom loader instead
# {'foo': ['bar', 'baz'], 'bacon': 'eggs'}

Extending the default dumper

Extending the dumper follows a similar principle as extending the loader.

As an example, I’ll make a custom dumper that dumps booleans True and False to integers instead of the JSON true or false.

from json5.dumper import DefaultDumper, dumps

class MyCustomDumper(DefaultDumper):
    def dump(self, node):
        if isinstance(node, bool):
            return self.bool_to_json(node)
        else:
            return super().dump(node)

    def bool_to_json(self, node):
        super().dump(int(node.value))

And you can see the effects

>>> dumps([True, False])
'[true, false]'
>>> dumps([True, False], dumper=MyCustomDumper())
'[1, 2]'

Other loaders/dumpers and tools

Besides the default loader, there is also the ModelLoader which simply returns the raw model with no additional processing.

Besides the default dumper, there is also the ModelDumper which takes a model and serializes it to JSON.

The json5.dumper.modelize function can take python objects and convert them to a model.

from json5.dumper import modelize
obj = ['foo', 123, True]
modelize(obj)

The resulting model:

JSONArray(
    values=[
        SingleQuotedString(characters='foo', raw_value="'foo'"),
        Integer(raw_value='123', value=123, is_hex=False),
        BooleanLiteral(value=True),
    ],
    trailing_comma=None,
)