Single-dispatch generic functions will be added to Python 3.4 (as proposed in PEP 443). When reading about single-dispatch functions I immediately thought of the difficulties I’ve had with custom JSON encoders. Below I explain why custom JSON encoders can complicate your application and how single-dispatch functions could be used to create a simpler JSON encoder.
JSON Encoding using the json library
With the current Python json library, using an extensible JSON encoder in your generic application may require some/all of the following:
- Allowing specification of custom encoder class by client applications
- Overriding the default JSON encoder class (or a client-specified one) for any further extensions
- Passing JSON encoder classes into other serialization libraries used by your application
Combining JSON encoders
If you need to compose two custom JSON encoders specified in two different packages, you may need to:
- Use multiple inheritance and hope the encoders play nicely together
- Duplicate code from one of the packages and create a new serializer with single inheritance
- Monkey patch one or both of the libraries
JSON encoder using single-dispatch generic functions
I created a wrapper around the json library to make a JSON encoder using single-dispatch generic functions. Here’s how to use it:
from decimal import Decimal
from json_singledispatch import encode
@encode.register(set)
def encode_set(obj):
return encode(list(obj))
@encode.register(Decimal)
def encode_decimal(obj):
return encode(str(obj))
print encode({'key': "value"})
print encode({5, 6})
print encode(Decimal("5.6"))
As you can see, it’s fairly easy to extend the encoder to understand serialization rules for new data types.
The impementation is fairly simple, albeit a hack:
import json
from singledispatch import singledispatch
class _CustomEncoder(json.JSONEncoder):
def default(self, obj):
for type_, handler in encode.registry.items():
if isinstance(obj, type_) and type_ is not object:
return handler(obj)
return super(_CustomEncoder, self).default(obj)
@singledispatch
def encode(obj, **kwargs):
return json.dumps(obj, cls=_CustomEncoder, **kwargs)
This code is intended as a proof-of-concept to demonstrate the power of single-dispatch generic functions. Feel free to use it however you like.