Use toJSON() Method to make class JSON serializable A simple and straightforward solution. Instead of making class JSON serializable, we can implement a serializer method in the class. So we don't need to write custom JSONEncoder. This new toJSON() serializer method will return the JSON representation of the Object.
I've used this strategy in the past and been pretty happy with it: Encode your custom objects as JSON object literals (like Python dict
s) with the following structure:
{ '__ClassName__': { ... } }
That's essentially a one-item dict
whose single key is a special string that specifies what kind of object is encoded, and whose value is a dict
of the instance's attributes. If that makes sense.
A very simple implementation of an encoder and a decoder (simplified from code I've actually used) is like so:
TYPES = { 'ParentClass': ParentClass,
'ChildClass': ChildClass }
class CustomTypeEncoder(json.JSONEncoder):
"""A custom JSONEncoder class that knows how to encode core custom
objects.
Custom objects are encoded as JSON object literals (ie, dicts) with
one key, '__TypeName__' where 'TypeName' is the actual name of the
type to which the object belongs. That single key maps to another
object literal which is just the __dict__ of the object encoded."""
def default(self, obj):
if isinstance(obj, TYPES.values()):
key = '__%s__' % obj.__class__.__name__
return { key: obj.__dict__ }
return json.JSONEncoder.default(self, obj)
def CustomTypeDecoder(dct):
if len(dct) == 1:
type_name, value = dct.items()[0]
type_name = type_name.strip('_')
if type_name in TYPES:
return TYPES[type_name].from_dict(value)
return dct
In this implementation assumes that the objects you're encoding will have a from_dict()
class method that knows how to take recreate an instance from a dict
decoded from JSON.
It's easy to expand the encoder and decoder to support custom types (e.g. datetime
objects).
EDIT, to answer your edit: The nice thing about an implementation like this is that it will automatically encode and decode instances of any object found in the TYPES
mapping. That means that it will automatically handle a ChildClass like so:
class ChildClass(object):
def __init__(self):
self.foo = 'foo'
self.bar = 1.1
self.parent = ParentClass(1)
That should result in JSON something like the following:
{ '__ChildClass__': {
'bar': 1.1,
'foo': 'foo',
'parent': {
'__ParentClass__': {
'foo': 1}
}
}
}
An instance of a custom class could be represented as JSON formatted string with help of following function:
def json_repr(obj):
"""Represent instance of a class as JSON.
Arguments:
obj -- any object
Return:
String that reprent JSON-encoded object.
"""
def serialize(obj):
"""Recursively walk object's hierarchy."""
if isinstance(obj, (bool, int, long, float, basestring)):
return obj
elif isinstance(obj, dict):
obj = obj.copy()
for key in obj:
obj[key] = serialize(obj[key])
return obj
elif isinstance(obj, list):
return [serialize(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(serialize([item for item in obj]))
elif hasattr(obj, '__dict__'):
return serialize(obj.__dict__)
else:
return repr(obj) # Don't know how to handle, convert to string
return json.dumps(serialize(obj))
This function will produce JSON-formatted string for
an instance of a custom class,
a dictionary that have instances of custom classes as leaves,
If you are using Django, it can be easily done via Django's serializers module. More info can be found here: https://docs.djangoproject.com/en/dev/topics/serialization/
As specified in python's JSON docs // help(json.dumps)
// >
You should simply override the default()
method of JSONEncoder
in order to provide a custom type conversion, and pass it as cls
argument.
Here is one I use to cover Mongo's special data types (datetime and ObjectId)
class MongoEncoder(json.JSONEncoder):
def default(self, v):
types = {
'ObjectId': lambda v: str(v),
'datetime': lambda v: v.isoformat()
}
vtype = type(v).__name__
if vtype in types:
return types[type(v).__name__](v)
else:
return json.JSONEncoder.default(self, v)
Calling it as simple as
data = json.dumps(data, cls=MongoEncoder)
This is kind of hackish and I'm sure there's probably a lot that can be wrong with it. However, I was producing a simple script and I ran the issue that I did not want to subclass my json serializer to serialize a list of model objects. I ended up using list comprehension
Let: assets = list of modelobjects
Code:
myJson = json.dumps([x.__dict__ for x in assets])
So far seems to have worked charmingly for my needs
I have a similar problem but the json.dump
function is not called by me.
So, to make MyClass
JSON serializable without giving a custom encoder to json.dump
you have to Monkey patch the json encoder.
First create your encoder in your module my_module
:
import json
class JSONEncoder(json.JSONEncoder):
"""To make MyClass JSON serializable you have to Monkey patch the json
encoder with the following code:
>>> import json
>>> import my_module
>>> json.JSONEncoder.default = my_module.JSONEncoder.default
"""
def default(self, o):
"""For JSON serialization."""
if isinstance(o, MyClass):
return o.__repr__()
else:
return super(self,o)
class MyClass:
def __repr__(self):
return "my class representation"
Then as it is described in the comment, monkey patch the json encoder:
import json
import my_module
json.JSONEncoder.default = my_module.JSONEncoder.default
Now, even an call of json.dump
in an external library (where you cannot change the cls
parameter) will work for your my_module.MyClass
objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With