In most of the cases, this is a two way problem, if you make use of a custom encoder you'll probably want to have a custom decoder (and vice-versa). In this case the decoder should be able to parse the encoded data and return the original json object.
Below there's a ful excersise to convert python non-serializable objects to json using 2 different strategies:
- Patching the JSONEncoder class to serializa any class that implements a "json" method to serialize classes.
- Using a list of "Converters" methods to seralize specific python types.
in the example below, I serialize a Enum class using a custom json method as {enum.name: enum.value} dict, here the enun.value objects are non serializable types in python (date and tuple), by using the methods listed CONVERTERS we can convert these types to serializable types.
Once encoded, the custom_json_decoder method can be invoked to convert that json back to python primitive types.
This script exaple below is complete, it should run "as is":
from enum import Enum
from dateutil.parser import parse as dtparse
from datetime import datetime
from datetime import date
from json import JSONEncoder
from json import loads as json_loads
from json import dumps as json_dumps
def wrapped_default(self, obj):
json_parser = getattr(obj.__class__, "__json__", lambda x: x.__dict__)
try:
return json_parser(obj)
except Exception:
return wrapped_default.default(obj)
wrapped_default.default = JSONEncoder().default
JSONEncoder.default = wrapped_default
CONVERTERS = {
"datetime": dtparse,
"date": lambda x: datetime.strptime(x, "%Y%m%d").date(),
"tuple": lambda x: tuple(x),
}
class RskJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, date):
return {"val": obj.strftime("%Y%m%d"), "pythontype": "date"}
elif isinstance(obj, datetime):
return {"val": obj.isoformat(), "pythontype": "datetime"}
elif isinstance(obj, tuple):
return {"val": list(obj), "pythontype": "tuple"}
return super().default(obj)
def custom_json_decoder(obj):
def json_hook(json_obj):
try:
return CONVERTERS[json_obj.pop("pythontype")](json_obj["val"])
except Exception:
res = json_obj
return res
return json_loads(obj, object_hook=json_hook)
def custom_json_encoder(obj):
return json_dumps(obj, cls=RskJSONEncoder)
if __name__ == "__main__":
class Test(Enum):
A = date(2021, 1, 1)
B = ("this", " is", " a", " tuple")
def __json__(self):
return {self.name: self.value}
d = {"enum_date": Test.A, "enum_tuple": Test.B}
this_is_json = custom_json_encoder(d)
this_is_python_obj = custom_json_decoder(this_is_json)
print(f"this is json, type={type(this_is_json)}\n", this_is_json)
print(
f"this is python, type={type(this_is_python_obj)}\n",
this_is_python_obj,
)