Convert internal objects to a serializable representation.

    During serialization if the object has the hook method `to_dict` it will be
    automatically called and metadata for decoding will be added. This allows
    for the translation of objects trees of arbitrary depth. E.g.:

    >>> class Root:
    >>>     def __init__(self, left, right):
    >>>         self.left = left
    >>>         self.right = right
    >>>     def to_dict(self):
    >>>         return {
    >>>           'left': left,
    >>>           'right': right,
    >>>         }
    >>> class Node:
    >>>     def to_dict(self):
    >>>         return {'value': 'node'}
    >>> root = Root(left=None(), right=None())
    >>> json.dumps(root, default=to_dict_hook)
    '{
        "_type": "Root",
        "left": {"_type": "Node", "value": "node"},
        "right": {"_type": "Node", "value": "node"}
    }'

def to_dict_hook(obj):
    """Convert internal objects to a serializable representation.

    During serialization if the object has the hook method `to_dict` it will be
    automatically called and metadata for decoding will be added. This allows
    for the translation of objects trees of arbitrary depth. E.g.:

    >>> class Root:
    >>>     def __init__(self, left, right):
    >>>         self.left = left
    >>>         self.right = right
    >>>     def to_dict(self):
    >>>         return {
    >>>           'left': left,
    >>>           'right': right,
    >>>         }
    >>> class Node:
    >>>     def to_dict(self):
    >>>         return {'value': 'node'}
    >>> root = Root(left=None(), right=None())
    >>> json.dumps(root, default=to_dict_hook)
    '{
        "_type": "Root",
        "left": {"_type": "Node", "value": "node"},
        "right": {"_type": "Node", "value": "node"}
    }'
    """
    if hasattr(obj, 'to_dict'):
        result = obj.to_dict()
        assert isinstance(result, dict), 'to_dict must return a dictionary'
        result['_type'] = f'{obj.__module__}.{obj.__class__.__name__}'
        result['_version'] = 0
        return result

    raise TypeError(
        f'Object of type {obj.__class__.__name__} is not JSON serializable',
    )