The default way to serialize custom non-serializable objects to JSON is to subclass json.JSONEncoder and pass a custom encoder to json.dumps(). This typically looks like the following:
<code class="python">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))</code>
However, what if you want to make an object serializable with the default encoder? After reviewing the json module's source code, it appears that extending the encoder directly will not meet this requirement.
Instead, you can use a technique called "monkey-patching" within your package's __init__.py initialization script. This affects all subsequent JSON module serializations since modules are generally loaded only once, and the result is cached in sys.modules.
The patch would modify the default JSON encoder's default method to check for a unique "to_json" method and utilize it to encode the object if found.
Here's an example implemented as a standalone module for simplicity:
<code class="python"># Module: make_json_serializable.py from json import JSONEncoder def _default(self, obj): return getattr(obj.__class__, "to_json", _default.default)(obj) _default.default = JSONEncoder.default # Save unmodified default. JSONEncoder.default = _default # Replace it.</code>
Using this patch is simple: just import the module to apply the monkey-patch.
<code class="python"># Sample client script import json import make_json_serializable # apply monkey-patch class Foo(object): def __init__(self, name): self.name = name def to_json(self): # New special method. """Convert to JSON format string representation.""" return '{"name": "%s"}' % self.name foo = Foo('sazpaz') print(json.dumps(foo)) # -> '{"name": "sazpaz"}'</code>
To retain object type information, the to_json method can include it in the returned string:
<code class="python">def to_json(self): """Convert to JSON format string representation.""" return '{"type": "%s", "name": "%s"}' % (self.__class__.__name__, self.name)</code>
This produces JSON that includes the class name:
{"type": "Foo", "name": "sazpaz"}
An even more powerful approach is to have the replacement default method serialize most Python objects automatically, including user-defined class instances, without requiring a unique method.
After researching several alternatives, the following approach based on pickle appears closest to this ideal:
<code class="python"># Module: make_json_serializable2.py from json import JSONEncoder import pickle def _default(self, obj): return {"_python_object": pickle.dumps(obj)} JSONEncoder.default = _default # Replace with the above.</code>
While not everything can be pickled (e.g., extension types), pickle provides methods to handle them via a protocol using unique methods. However, this approach covers more cases.
Using the pickle protocol simplifies reconstructing the original Python object by providing a custom object_hook function argument to json.loads() when encountering a "_python_object" key in the dictionary.
<code class="python">def as_python_object(dct): try: return pickle.loads(str(dct['_python_object'])) except KeyError: return dct pyobj = json.loads(json_str, object_hook=as_python_object)</code>
This can be simplified to a wrapper function:
<code class="python">json_pkloads = functools.partial(json.loads, object_hook=as_python_object) pyobj = json_pkloads(json_str)</code>
This code does not work in Python 3 because json.dumps() returns a bytes object that JSONEncoder cannot handle. However, the approach remains valid with the following modification:
<code class="python">def _default(self, obj): return {"_python_object": pickle.dumps(obj).decode('latin1')} def as_python_object(dct): try: return pickle.loads(dct['_python_object'].encode('latin1')) except KeyError: return dct</code>
The above is the detailed content of How can I make custom objects JSON serializable without subclassing `json.JSONEncoder`?. For more information, please follow other related articles on the PHP Chinese website!