The regular way of JSON-serializing custom non-serializable objects is to subclass json.JSONEncoder
and then pass a custom encoder to dumps.
It usually looks like this:
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, foo):
return obj.to_json()
return json.JSONEncoder.default(self, obj)
print json.dumps(obj, cls = CustomEncoder)
What I'm trying to do, is to make something serializable with the default encoder. I looked around but couldn't find anything.
My thought is that there would be some field in which the encoder looks at to determine the json encoding. Something similar to __str__
. Perhaps a __json__
field.
Is there something like this in python?
I want to make one class of a module I'm making to be JSON serializable to everyone that uses the package without them worrying about implementing their own [trivial] custom encoders.
As I said in a comment to your question, after looking at the
json
module's source code, it does not appear to lend itself to doing what you want. However the goal could be achieved by what is known as monkey-patching (see question What is a monkey patch?). This could be done in your package's__init__.py
initialization script and would affect all subsequentjson
module serialization since modules are generally only loaded once and the result is cached insys.modules
.The patch changes the default json encoder's
default
method—the defaultdefault()
.Here's an example implemented as a standalone module for simplicity's sake:
Module:
make_json_serializable.py
Using it is trivial since the patch is applied by simply importing the module.
Sample client script:
To retain the object type information, the special method can also include it in the string returned:
Which produces the following JSON that now includes the class name:
Magick Lies Here
Even better than having the replacement
default()
look for a specially named method, would be for it to be able to serialize most Python objects automatically, including user-defined class instances, without needing to add a special method. After researching a number of alternatives, the following which uses thepickle
module, seemed closest to that ideal to me:Module:
make_json_serializable2.py
Of course everything can't be pickled—extension types for example. However there are ways defined to handle them via the pickle protocol by writing special methods—similar to what you suggested and I described earlier—but doing that would likely be necessary for a far fewer number of cases.
Regardless, using the pickle protocol also means it would be fairly easy to reconstruct the original Python object by providing a custom
object_hook
function argument on anyjson.loads()
calls that looked for a'_python_object'
key in the dictionary passed in. Something like:If this has to be done in many places, it might be worthwhile to define a wrapper function that automatically supplied the extra keyword argument:
Naturally, this could be monkey-patched it into the
json
module as well, making the function the defaultobject_hook
(instead ofNone
).I got the idea for using
pickle
from an answer by Raymond Hettinger to another JSON serialization question, whom I consider exceptionally credible as well as an official source (as in Python core developer).Portablity to Python 3
The code above does not work as shown in Python 3 because
json.dumps()
returns abytes
object which theJSONEncoder
can't handle. However the approach is still valid. A simple way to workaround the issue is tolatin1
"decode" the value returned frompickle.dumps()
and then "encode" it fromlatin1
before passing it on topickle.loads()
in theas_python_object()
function. This works because arbitrary binary strings are validlatin1
which can always be decoded to Unicode and then encoded back to the original string again (as pointed out in this answer by Sven Marnach).(Although the following works fine in Python 2, the
latin1
decoding and encoding it does is superfluous.)I suggest putting the hack into the class definition. This way, once the class is defined, it supports JSON. Example:
The problem with overriding
JSONEncoder().default
is that you can do it only once. If you stumble upon anything a special data type that does not work with that pattern (like if you use a strange encoding). With the pattern below, you can always make your class JSON serializable, provided that the class field you want to serialize is serializable itself (and can be added to a python list, barely anything). Otherwise, you have to apply recursively the same pattern to your json field (or extract the serializable data from it):You can extend the dict class like so:
Now to make your classes serializable with the regular encoder, extend 'Serializable':
print(obj)
will print something like:print(json.dumps(obj, indent=4))
will print something like:I don't understand why you can't write a
serialize
function for your own class? You implement the custom encoder inside the class itself and allow "people" to call the serialize function that will essentially returnself.__dict__
with functions stripped out.edit:
This question agrees with me, that the most simple way is write your own method and return the json serialized data that you want. They also recommend to try jsonpickle, but now you're adding an additional dependency for beauty when the correct solution comes built in.