How to Make a Class Json Serializable
How to Make a Python Class Serializable? Class Fileitem: Def __Init__(Self, Fname): Self. Fname = Fname Attempt to Serialize to Json: >>> Import Json >>> X =...
How to make a Python class serializable?
class FileItem:
def __init__(self, fname):
self.fname = fname
Attempt to serialize to JSON:
>>> import json
>>> x = FileItem('/foo/bar')
>>> json.dumps(x)
TypeError: Object of type 'FileItem' is not JSON serializable
38 Answers
Here is a simple solution for a simple feature:
Must Read
.toJSON() Method
Instead of a JSON serializable class, implement a serializer method:
import json
class Object:
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
So you just call it to serialize:
me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"
print(me.toJSON())
will output:
{
"age": 35,
"dog": {
"name": "Apollo"
},
"name": "Onur"
}
Do you have an idea about the expected output? For example, will this do?
>>> f = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'
In that case you can merely call json.dumps(f.__dict__).
If you want more customized output then you will have to subclass JSONEncoder and implement your own custom serialization.
For a trivial example, see below.
>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'
Then you pass this class into the json.dumps() method as cls kwarg:
json.dumps(cls=MyEncoder)
If you also want to decode then you'll have to supply a custom object_hook to the JSONDecoder class. For example:
>>> def from_json(json_object):
if 'fname' in json_object:
return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>>
For more complex classes you could consider the tool jsonpickle:
jsonpickle is a Python library for serialization and deserialization of complex Python objects to and from JSON.
The standard Python libraries for encoding Python into JSON, such as the stdlib’s json, simplejson, and demjson, can only handle Python primitives that have a direct JSON equivalent (e.g. dicts, lists, strings, ints, etc.). jsonpickle builds on top of these libraries and allows more complex data structures to be serialized to JSON. jsonpickle is highly configurable and extendable–allowing the user to choose the JSON backend and add additional backends.
Most of the answers involve changing the call to json.dumps(), which is not always possible or desirable (it may happen inside a framework component for example).
If you want to be able to call json.dumps(obj) as is, then a simple solution is inheriting from dict:
class FileItem(dict):
def __init__(self, fname):
dict.__init__(self, fname=fname)
f = FileItem('tasks.txt')
json.dumps(f) #No need to change anything here
This works if your class is just basic data representation, for trickier things you can always set keys explicitly.
As mentioned in many other answers you can pass a function to json.dumps to convert objects that are not one of the types supported by default to a supported type. Surprisingly none of them mentions the simplest case, which is to use the built-in function vars to convert objects into a dict containing all their attributes:
json.dumps(obj, default=vars)
Note that this covers only basic cases, if you need more specific serialization for certain types (e.g. exluding certain attributes or for objects that don't have a __dict__ attribute) you need to use a custom function or a JSONEncoder as desribed in the other answers.
Just add to_json method to your class like this:
def to_json(self):
return self.message # or how you want it to be serialized
And add this code (from this answer), to somewhere at the top of everything:
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default
JSONEncoder.default = _default
This will monkey-patch json module when it's imported, so
JSONEncoder.default() automatically checks for a special to_json()
method and uses it to encode the object if found.
Just like Onur said, but this time you don't have to update every json.dumps() in your project.
I like Onur's answer but would expand to include an optional toJSON() method for objects to serialize themselves:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Another option is to wrap JSON dumping in its own class:
import json
class FileItem:
def __init__(self, fname):
self.fname = fname
def __repr__(self):
return json.dumps(self.__dict__)
Or, even better, subclassing FileItem class from a JsonSerializable class:
import json
class JsonSerializable(object):
def toJson(self):
return json.dumps(self.__dict__)
def __repr__(self):
return self.toJson()
class FileItem(JsonSerializable):
def __init__(self, fname):
self.fname = fname
Testing:
>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
If you're using Python3.5+, you could use jsons. (PyPi: ) It will convert your object (and all its attributes recursively) to a dict.
import jsons
a_dict = jsons.dump(your_object)
Or if you wanted a string:
a_str = jsons.dumps(your_object)
Or if your class implemented jsons.JsonSerializable:
a_dict = your_object.json
I came across this problem the other day and implemented a more general version of an Encoder for Python objects that can handle nested objects and inherited fields:
import json
import inspect
class ObjectEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj, "to_json"):
return self.default(obj.to_json())
elif hasattr(obj, "__dict__"):
d = dict(
(key, value)
for key, value in inspect.getmembers(obj)
if not key.startswith("__")
and not inspect.isabstract(value)
and not inspect.isbuiltin(value)
and not inspect.isfunction(value)
and not inspect.isgenerator(value)
and not inspect.isgeneratorfunction(value)
and not inspect.ismethod(value)
and not inspect.ismethoddescriptor(value)
and not inspect.isroutine(value)
)
return self.default(d)
return obj
Example:
class C(object):
c = "NO"
def to_json(self):
return {"c": "YES"}
class B(object):
b = "B"
i = "I"
def __init__(self, y):
self.y = y
def f(self):
print "f"
class A(B):
a = "A"
def __init__(self):
self.b = [{"ab": B("y")}]
self.c = C()
print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)
Result:
{
"a": "A",
"b": [
{
"ab": {
"b": "B",
"i": "I",
"y": "y"
}
}
],
"c": {
"c": "YES"
},
"i": "I"
}
The Real Answer to "making a *Class* serializable"
_
TLDR: while you can copy-paste Option 2 (below) Option 1 is better
Explanation:
- While there is a viable solution, there is no python "official" solution.
- By official solution, I mean there is no way (as of 2022) to add a method to your class (like
toJSONin JavaScript) and no way to register your class with the built-in json module. When something likejson.dumps([1,2, your_obj])is executed, python doesn't check a lookup table or object method. - I'm not sure why other answers don't explain this
- The closest official approach is probably andyhasit's answer which is to inherit from a dictionary. However, inheriting from a dictionary doesn't work very well for many custom classes like AdvancedDateTime, or pytorch tensors.
- By official solution, I mean there is no way (as of 2022) to add a method to your class (like
- The ideal workaround is this:
- Mutate
json.dumps(affects everywhere, even pip modules that import json) - Add
def __json__(self)method to your class
- Mutate
_
Option 1: Let a Module do the Patching
(extended + packaged version of Fancy John's answer, thank you @FancyJohn)
pip install json-fix
Step 1:
your_class_definition.py
import json_fix
class YOUR_CLASS:
def __json__(self):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is naturally json-able"
Step 2:
- There is no step 2. It just works.
(see option 2 if you want an explanation)
Example usage:
from your_class_definition import YOUR_CLASS
import json
json.dumps([1,2, YOUR_CLASS()], indent=0)
# '[\n1,\n2,\n"a built-in object that is naturally json-able"\n]'
_
For Pandas DataFrames, Numpy arrays, and other 3rd party objects you want to be json-able, see the Module on how to make them json-able with ~2 lines of code.
_
Option 2: Patch json.dumps yourself
Note: this approach is simplified, and misses out on controlling the json behavior for external classes (numpy arrays, datetime, dataframes, tensors, etc).
some_file_thats_imported_before_your_class_definitions.py
# Step: 1
# create the patch
from json import JSONEncoder
def wrapped_default(self, obj):
return getattr(obj.__class__, "__json__", wrapped_default.default)(obj)
wrapped_default.default = JSONEncoder().default
# apply the patch
JSONEncoder.original_default = JSONEncoder.default
JSONEncoder.default = wrapped_default
your_class_definition.py
# Step 2
class YOUR_CLASS:
def __json__(self, **options):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is natually json-able"
_
All other answers seem to be "Best practices/approaches to serializing a custom object"
- Which, is alreadly covered here in the docs (search "complex" for an example of encoding complex numbers)
import simplejson
class User(object):
def __init__(self, name, mail):
self.name = name
self.mail = mail
def _asdict(self):
return self.__dict__
print(simplejson.dumps(User('alice', '')))
if using standard json, you need to define a default function
import json
def default(o):
return o._asdict()
print(json.dumps(User('alice', ''), default=default))
json is limited in terms of objects it can print, and jsonpickle (you may need a pip install jsonpickle) is limited in terms it can't indent text. If you would like to inspect the contents of an object whose class you can't change, I still couldn't find a straighter way than:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
Note: that still they can't print the object methods.
Here is my 3 cents ...
This demonstrates explicit json serialization for a tree-like python object.
Note: If you actually wanted some code like this you could use the twisted
FilePath class.
import json, sys, os
class File:
def __init__(self, path):
self.path = path
def isdir(self):
return os.path.isdir(self.path)
def isfile(self):
return os.path.isfile(self.path)
def children(self):
return [File(os.path.join(self.path, f))
for f in os.listdir(self.path)]
def getsize(self):
return os.path.getsize(self.path)
def getModificationTime(self):
return os.path.getmtime(self.path)
def _default(o):
d = {}
d['path'] = o.path
d['isFile'] = o.isfile()
d['isDir'] = o.isdir()
d['mtime'] = int(o.getModificationTime())
d['size'] = o.getsize() if o.isfile() else 0
if o.isdir(): d['children'] = o.children()
return d
folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)
This class can do the trick, it converts object to standard json .
import json
class Serializer(object):
@staticmethod
def serialize(object):
return json.dumps(object, default=lambda o: o.__dict__.values()[0])
usage:
Serializer.serialize(my_object)
working in python2.7 and python3.
import json
class Foo(object):
def __init__(self):
self.bar = 'baz'
self._qux = 'flub'
def somemethod(self):
pass
def default(instance):
return {k: v
for k, v in vars(instance).items()
if not str(k).startswith('_')}
json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo
print(json_foo)