def METHOD_NAME(cls, val):
    """
    Recursively replace all instances of encodable types with their encoded
    value. This is useful over the ``default=`` functionality of the JSON
    encoder because JSON will not call default for tuples, lists, ints, etc:
    https://docs.python.org/3/library/json.html#json.JSONEncoder
    Cannot handle circular references.
    """
    if isinstance(val, tuple(cls.encoders.keys())):
        # we can't directly index into the encoders dict because
        # we need to be able to handle subclasses
        encoder = next(
            encoder for typ, encoder in cls.encoders.items() if isinstance(val, typ)
        )
        return cls.METHOD_NAME(encoder(val))
    elif isinstance(val, dict):
        return {k: cls.METHOD_NAME(v) for k, v in val.items()}
    elif isinstance(val, (tuple, list)):
        return [cls.METHOD_NAME(v) for v in val]
    elif isinstance(val, (bool, int, float, str)) or val is None:
        return val
    else:
        raise TypeError(f"Unserializable type {type(val)}")