Source code for bricks.json.common

import base64
import datetime
import json as _json

from markupsafe import Markup

from .util import normalize_class_name
from .decoders import decode, register as register_decode
from .encoders import encode, register as register_encode


[docs]def register(cls, name=None, encode=None, decode=None): """ Register encode/decode pair of functions for the given Python type. Registration extends Bricks flavored JSON to handle arbitrary python objects. Args: cls: python data type name: name associated with the '@' when encoded to JSON. encode: the encode function; convert object to JSON. The resulting JSON can have non-valid JSON types as long as they can be also converted to JSON using the :func:`bricks.json.encode` function. decode: decode function; converts JSON back to Python. The decode function might assume that all elements were already converted to their most Pythonic forms (i.e., all dictionaries with an '@' key were already decoded to their Python forms). See also: :ref:`json-custom-types` """ name = normalize_class_name(cls, name) if (encode is None and decode is not None or decode is None and encode is not None): raise ValueError('encoder and decoder must be given') if encode is None: def decorator(func): def decode(dec_func): register_decode(cls, name, dec_func) return dec_func func.register_decoder = decode register_encode(cls, name, func) return func return decorator register_decode(cls, name, decode) register_encode(cls, name, encode)
[docs]def loads(data): """ Load a string of JSON-encoded data and return the corresponding Python object. """ raw = _json.loads(data) return decode(raw)
[docs]def dumps(obj): """ Return a JSON string dump of a Python object. """ encoded = encode(obj) return _json.dumps(encoded)
# # Python builtin types # @register(bytes) def encode_bytes(data): data = base64.b64encode(data).decode('ascii') return {'data': data} @encode_bytes.register_decoder def decode_bytes(data): data = data['data'].encode('ascii') return base64.b64decode(data) @register(set) def encode_set(data): return {'data': encode(list(data))} @encode_set.register_decoder def decode_set(data): return set(data['data']) @register(tuple) def encode_tuple(data): return {'data': list(data)} @encode_tuple.register_decoder def decode_tuple(data): return tuple(data['data']) @register(list) def encode_list(data): return [encode(x) for x in data] @encode_list.register_decoder def decode_list(data): return tuple(data['data']) # Dictionaries need special treatment because we want to be able to serialize # dictionaries with non-string key. Dictionaries with an '@' must also be # supported @register(dict, 'dict') def encode_dict(data): if '@' in data or not all(isinstance(k, str) for k in data.keys()): result = {'data': [[encode(k), encode(v)] for (k, v) in data.items()]} result['@'] = 'dict' return result return {k: encode(v) for (k, v) in data.items()} @encode_dict.register_decoder def decode_dict(data): result = {} for k, v in data['data']: result[decode(k)] = decode(v) return result # # Common Python types (not builtins) # @register(datetime.date, 'date') def encode_datetime(date): return { '@': 'date', 'year': date.year, 'month': date.month, 'day': date.day } @encode_datetime.register_decoder def decode_datetime(data): return datetime.date(data['year'], data['month'], data['day']) # # Contrib types (not on standard lib) # @register(Markup, 'markup') def encode_markup(x): return {'data': str(x)} @encode_markup.register_decoder def decode_markup(x): return Markup(x['data'])