Paudirac's

dumps

>>> import json
>>> json.dumps(42)
'42'
>>> type(_)
<class 'str'>
simple, things should be simple
>>> import json
>>> json.dumps({
...     'name': 'H',
...     'mass': {'value': 1.00784, 'units': 'Da'},
...     'atomic number': 1,
...     'constituents': ['proton', 'electron']
... })
...
'{"name": "H", "mass": {"value": 1.00784, "units": "Da"}, "atomic number": 1, "constituents": ["proton", "electron"]}'
>>> type(_)
<class 'str'>
complex things should be possible
>>> import uuid
>>> the_id = uuid.uuid4()
>>> the_id
UUID('af281d69-6cca-49f5-b119-abccd46bb32c')
>>> json.dumps({'the_id': the_id})
Traceback (most recent call last):
  ...
TypeError: Object of type UUID is not JSON serializable
when serializing dict item 'the_id'
errors should never pass silently
>>> import dataclasses
>>> @dataclasses.dataclass
... class Atom:
...     name: str
...     mass: tuple
...     atomic_number: int
...     constituents: list[str]
...
>>> hydrogen = Atom('H', (1.00784, 'Da'), 1, ['proton', 'electron'])
>>> hydrogen
Atom(name='H', mass=(1.00784, 'Da'), atomic_number=1, constituents=['proton', 'electron'])
>>> json.dumps(hydrogen)
Traceback (most recent call last):
  ...
TypeError: Object of type Atom is not JSON serializable
errors should never pass silently (2)
>>> help(json.dumps)
Help on function dumps in module json:

dumps(
    obj,
    *,
    skipkeys=False,
    ensure_ascii=True,
    check_circular=True,
    allow_nan=True,
    cls=None,
    indent=None,
    separators=None,
    default=None,
    sort_keys=False,
    **kw
)
  ...
    ``default(obj)`` is a function that should return a serializable version
    of obj or raise TypeError. The default simply raises TypeError.
  ...
>>> print(chr(0x1F914))
πŸ€”
>>> def nondefault(obj):
...     print(f'{type(obj)}')
...     return 42
...
>>> json.dumps({'the_id': the_id}, default=nondefault)
<class 'uuid.UUID'>
'{"the_id": 42}'
>>> json.dumps(hydrogen, default=nondefault)
<class '__main__.Atom'>
'42'
unless explicitly silenced
>>> print(chr(0x1F4A1))
πŸ’‘
>>> def nondefault(obj):
...     if isinstance(obj, uuid.UUID):
...         return str(obj)
...     elif isinstance(obj, Atom):
...         return dataclasses.asdict(obj)
...     else:
...         return obj
...
>>> json.dumps({'the_id': the_id}, default=nondefault)
'{"the_id": "af281d69-6cca-49f5-b119-abccd46bb32c"}'
>>> json.dumps(hydrogen, default=nondefault)
'{"name": "H", "mass": [1.00784, "Da"], "atomic_number": 1, "constituents": ["proton", "electron"]}'
unless explicitly silenced (2)
>>> from functools import singledispatch
>>> help(singledispatch)
Help on function singledispatch in module functools:

singledispatch(func)
    Single-dispatch generic function decorator.

    Transforms a function into a generic function, which can have different
    behaviours depending upon the type of its first argument. The decorated
    function acts as the default implementation, and additional
    implementations can be registered using the register() attribute of the
    generic function.
>>> print(chr(0x1F4A1))
πŸ’‘
>>> import typing
>>> @singledispatch
... def to_dict(obj: typing.Any) -> dict:
...     raise NotImplementedError("Unsupported type: {obj_type}".format(obj_type=type(obj)))
...
>>> print(chr(0x1F937))
🀷
if the implementation is hard to explain, it's a bad idea
>>> to_dict(the_id)
Traceback (most recent call last):
  ...
NotImplementedError: Unsupported type: <class 'uuid.UUID'>
>>> to_dict(hydrogen)
Traceback (most recent call last):
  ...
NotImplementedError: Unsupported type: <class '__main__.Atom'>
>>> print(chr(0x1F914))
πŸ€”
>>> @to_dict.register
... def _(obj: uuid.UUID) -> dict:
...     return {'__type__': 'uuid.UUID', 'value': str(obj)}
...
>>> @to_dict.register
... def _(obj: Atom) -> dict:
...     return dataclasses.asdict(obj)
...
>>> to_dict(the_id)
{'__type__': 'uuid.UUID', 'value': 'f2078f61-4e9d-4af8-ace0-2f03961fa59d'}
>>> to_dict(hydrogen)
{"name": "H", "mass": [1.00784, "Da"], "atomic_number": 1, "constituents": ["proton", "electron"]}
>>> print(chr(0x1F604))
πŸ˜„
if the implementation is easy to explain, it may be a good idea
>>> print(chr(0x1F914), chr(0x1F4A1))
πŸ€” πŸ’‘
>>> def dumps(obj: typing.Any, default=to_dict, **kwargs) -> str:
...     return json.dumps(obj, default=default, **kwargs)
...
>>> dumps({'the_id': the_id})
'{"the_id": {"__type__": "uuid.UUID", "value": "af281d69-6cca-49f5-b119-abccd46bb32c"}}'
>>> dumps(hydrogen)
'{"name": "H", "mass": [1.00784, "Da"], "atomic_number": 1, "constituents": ["proton", "electron"]}'
>>> print(dumps({'the_id': the_id}, indent=2))
{
  "the_id": {
    "__type__": "uuid.UUID",
    "value": "af281d69-6cca-49f5-b119-abccd46bb32c"
  }
}
>>> print(dumps(hydrogen, indent=2))
{
  "name": "H",
  "mass": [
    1.00784,
    "Da"
  ],
  "atomic_number": 1,
  "constituents": [
    "proton",
    "electron"
  ]
}
>>> print(chr(0x1F44C))
πŸ‘Œ
there should be one-- and preferably only one --obvious way to do it