Python how to make object json serializable

Making object JSON serializable with regular encoder

The regular way of JSON-serializing custom non-serializable objects is to subclass json.JSONEncoder and then pass a custom encoder to json.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.

6 Answers 6

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 subsequent json module serialization since modules are generally only loaded once and the result is cached in sys.modules .

The patch changes the default json encoder’s default method—the default default() .

Here’s an example implemented as a standalone module for simplicity’s sake:

""" Module that monkey-patches 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. """ 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. 

Using it is trivial since the patch is applied by simply importing the module.

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 '' % self.name foo = Foo('sazpaz') print(json.dumps(foo)) # -> "" 

To retain the object type information, the special method can also include it in the string returned:

 return ('' % (self.__class__.__name__, self.name)) 

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 — based on an answer by @Raymond Hettinger to another question — which uses the pickle module, seemed closest to that ideal to me:

""" Module that imports the json module and monkey-patches it so JSONEncoder.default() automatically pickles any Python objects encountered that aren't standard JSON data types. """ from json import JSONEncoder import pickle def _default(self, obj): return JSONEncoder.default = _default # Replace with the above. 

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.

Читайте также:  Java изменить тип данных

Deserializing

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 any json.loads() calls that used any ‘_python_object’ key in the dictionary passed in, whenever it has one. Something like:

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) 

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:

json_pkloads = functools.partial(json.loads, object_hook=as_python_object) pyobj = json_pkloads(json_str) 

Naturally, this could be monkey-patched it into the json module as well, making the function the default object_hook (instead of None ).

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).

Portability to Python 3

The code above does not work as shown in Python 3 because json.dumps() returns a bytes object which the JSONEncoder can’t handle. However the approach is still valid. A simple way to workaround the issue is to latin1 «decode» the value returned from pickle.dumps() and then «encode» it from latin1 before passing it on to pickle.loads() in the as_python_object() function. This works because arbitrary binary strings are valid latin1 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.)

from decimal import Decimal class PythonObjectEncoder(json.JSONEncoder): def default(self, obj): return def as_python_object(dct): try: return pickle.loads(dct['_python_object'].encode('latin1')) except KeyError: return dct class Foo(object): # Some user-defined class. def __init__(self, name): self.name = name def __eq__(self, other): if type(other) is type(self): # Instances of same class? return self.name == other.name return NotImplemented __hash__ = None data = [1,2,3, set(['knights', 'who', 'say', 'ni']), , Foo('Bar'), Decimal('3.141592653589793238462643383279502884197169')] j = json.dumps(data, cls=PythonObjectEncoder, indent=4) data2 = json.loads(j, object_hook=as_python_object) assert data == data2 # both should be same 

Источник

Читайте также:  Основы языка html ответы

Serialize a Python Class Object to JSON

Serialize a Python Class Object to JSON

  1. Serialization in Python
  2. Make a Python Class JSON Serializable

This tutorial educates about the serialization process. It also illustrates how we can make a JSON class serializable using the toJSON() method and wrapping JSON to dump in its class.

Serialization in Python

The serialization process involves transforming an object into a format that can be preserved and retrieved later. Such as by recording the state of an object into a file.

All developers must do serialization at some point for all projects that are even remotely sophisticated. The Python programming language’s simplicity in many typical programming jobs is one of its excellent features.

With a few lines of code, file IO, drawing graphs, and accessing online content are all made simple. Likewise, python makes serialization simple, except when trying to serialize a custom class.

Let’s learn how to serialize your class objects to JSON objects below.

Make a Python Class JSON Serializable

The built-in json module of Python can handle only Python primitive types with a direct JSON counterpart.

Expressly, the default functionality of the JSON encoder, json.dump() and json.dumps() is limited to serializing the most basic set of object types (e.g., dictionary, lists, strings, numbers, None, etc.).

To fix this, we must create a unique encoder that allows our class JSON to be serializable.

It is possible to make a Python class JSON serializable in various methods. Choose the option that best fits the complexity of your situation.

Use the toJSON() Method to Make a JSON Class Serializable

We can implement the serializer method instead of the JSON serializable class. For example, in this method, the Object class might have a JSON function that returns a dictionary of the Object information.

Since all of the Object attributes are native types, we can send this dictionary into an encoding function to obtain the JSON notation. Finally, we return the values of the attributes that make up an object.

import json  class Object:  def toJSON(self):  return json.dumps(self, default=lambda o: o.__dict__,sort_keys=True, indent=4)  me = Object() me.name = "kelvin" me.age = 20 me.dog = Object() me.dog.name = "Brunno" print(me.toJSON()) 
  "age": 20,  "dog":   "name": "Brunno"  >,  "name": "kelvin" > 

Wrap JSON to Dump in Its Class

Another solution is to subclass a JsonSerializable class to create a FileItem class that wraps JSON dumping, as shown in the following code.

from typing import List from dataclasses import dataclass, asdict, field from json import dumps  @dataclass class Students:  id: 1  name: "stu1"   @property  def __dict__(self):  return asdict(self)   @property  def json(self):  return dumps(self.__dict__)  test_object_1 = Students(id=1, name="Kelvin") print(test_object_1.json) 

Zeeshan is a detail oriented software engineer that helps companies and individuals make their lives and easier with software solutions.

Related Article — Python JSON

Related Article — Python Class

Copyright © 2023. All right reserved

Источник

Make a Custom Class JSON serializable

I have a custom class, let’s call is class ObjectA(), and it have a bunch of functions, property, etc. and I want to serialize object using the standard json library in python, what do I have to implement that this object will serialize to JSON without write a custom encoder? Thank you

1 Answer 1

Subclass json.JSONEncoder, and then construct a suitable dictionary or array.

See «Extending JSONEncoder» behind this link

>>> class A: pass . >>> a = A() >>> a.foo = "bar" >>> import json >>> >>> class MyEncoder(json.JSONEncoder): . def default(self, obj): . if isinstance(obj, A): . return < "foo" : obj.foo >. return json.JSONEncoder.default(self, obj) . >>> json.dumps(a, cls=MyEncoder) '' 

Not possible without a custom encoder. If the premise of the question is that json doesn’t already know how to encode it, then any solution will involve a custom encoder. Maybe not one that extends JSONEncoder — you could write a simple f() that maps an object onto a dict — but what would that be, if not a custom encoder?

Should mention that in your answer so OP knows it’s not possible, and what the other options (if any) there are. Sorry if I came off a bit rude.

Seems a bit weak that you can’t do this without a custom encoder. I would think you could implement __dict__() or something like that and the JSON library would know how to handle it. I wanted to avoid custom encoders. I have about 30 custom classes.

If you implemented __dict__() and turned it into some custom dictionary. that would be a custom encoder. You just would have chosen to implement it in that way. If the library doesn’t automagically know how to convert your objects, then any extra code you write for that purpose is going to be a custom encoder by some other name.

@FrobberOfBits the difference between implementing a __dict__ method and a custom encoder is, that I can pass my object implementing __dict__ to a module which is already using the json library and it would accept it without throwing a «could not serialize» error. While I can not change the encoder this module is using. This is a world of a difference. Rust really has the right idea with their trait system. use case: custom configuration class, passed to databaseClient.add_table()

Источник

Оцените статью