Serializing class instance to JSON

389,245

Solution 1

The basic problem is that the JSON encoder json.dumps() only knows how to serialize a limited set of object types by default, all built-in types. List here: https://docs.python.org/3.3/library/json.html#encoders-and-decoders

One good solution would be to make your class inherit from JSONEncoder and then implement the JSONEncoder.default() function, and make that function emit the correct JSON for your class.

A simple solution would be to call json.dumps() on the .__dict__ member of that instance. That is a standard Python dict and if your class is simple it will be JSON serializable.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

The above approach is discussed in this blog posting:

    Serializing arbitrary Python objects to JSON using _dict_

And, of course, Python offers a built-in function that accesses .__dict__ for you, called vars().

So the above example can also be done as:

s = json.dumps(vars(foo)) # s set to: {"x":1, "y":2}

Solution 2

There's one way that works great for me that you can try out:

json.dumps() can take an optional parameter default where you can specify a custom serializer function for unknown types, which in my case looks like

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

First two ifs are for date and time serialization and then there is a obj.__dict__ returned for any other object.

the final call looks like:

json.dumps(myObj, default=serialize)

It's especially good when you are serializing a collection and you don't want to call __dict__ explicitly for every object. Here it's done for you automatically.

So far worked so good for me, looking forward for your thoughts.

Solution 3

You can specify the default named parameter in the json.dumps() function:

json.dumps(obj, default=lambda x: x.__dict__)

Explanation:

Form the docs (2.7, 3.6):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(Works on Python 2.7 and Python 3.x)

Note: In this case you need instance variables and not class variables, as the example in the question tries to do. (I am assuming the asker meant class instance to be an object of a class)

I learned this first from @phihag's answer here. Found it to be the simplest and cleanest way to do the job.

Solution 4

Using jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

Solution 5

I just do:

data=json.dumps(myobject.__dict__)

This is not the full answer, and if you have some sort of complicated object class you certainly will not get everything. However I use this for some of my simple objects.

One that it works really well on is the "options" class that you get from the OptionParser module. Here it is along with the JSON request itself.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)
Share:
389,245

Related videos on Youtube

ferhan
Author by

ferhan

I'm a developer located in Seattle.

Updated on August 01, 2022

Comments

  • ferhan
    ferhan over 1 year

    I am trying to create a JSON string representation of a class instance and having difficulty. Let's say the class is built like this:

    class testclass:
        value1 = "a"
        value2 = "b"
    

    A call to the json.dumps is made like this:

    t = testclass()
    json.dumps(t)
    

    It is failing and telling me that the testclass is not JSON serializable.

    TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable
    

    I have also tried using the pickle module :

    t = testclass()
    print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))
    

    And it gives class instance information but not a serialized content of the class instance.

    b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'
    

    What am I doing wrong?

    • CodeClown42
      CodeClown42 almost 12 years
    • codeman48
      codeman48 over 6 years
      Use one line, s = json.dumps(obj, default=lambda x: x.__dict__), to serialize object's instance variables (self.value1, self.value2, ...). Its the simplest and the most straight forward way. It will serialize nested object structures. The default function is called when any given object is not directly serializable. You can also look at my answer below. I found the popular answers unnecessarily complex, which were probably true quite a long time back.
    • martineau
      martineau about 6 years
      Your testclass has no __init__() method, so all instances will share the same two class attributes (value1 and value2) defined in the class statement. Do you understand the difference between a class and an instance of one?
    • best wishes
      best wishes almost 5 years
      There is a python library for this github.com/jsonpickle/jsonpickle (commenting since answer is too below in the thread and wont be reachable.)
  • ferhan
    ferhan almost 12 years
    I tried this. The end result of a call to json.dumps(t.__dict__) is just {}.
  • steveha
    steveha almost 12 years
    That is because your class doesn't have a .__init__() method function, so your class instance has an empty dictionary. In other words, {} is the correct result for your example code.
  • ferhan
    ferhan almost 12 years
    Thanks. This does the trick. I added a simple init with no parameters and now calling the json.dumps(t.__dict__) returns proper data in the format of: {"value2": "345", "value1": "123"} I had seen posts like this before, wasn't sure whether I needed a custom serializer for members, needing init wasn't mentioned explicitly or I missed it. Thank you.
  • SpiRail
    SpiRail almost 11 years
    You might want to remove self, if you are not using this inside a class.
  • Haroldo_OK
    Haroldo_OK almost 10 years
    The main problem I see with pickle is that it's a Python-specific format, while JSON is a platform-independant format. JSON is specially useful if you're writing either a web application or a backend for some mobile application. That having been said, thanks for pointing out to jsonpickle.
  • Haroldo_OK
    Haroldo_OK almost 10 years
    That will work okay, as long as the object isn't composed of other objects.
  • Caelum
    Caelum over 8 years
    @Haroldo_OK Doesn't jsonpickle still export to JSON, just not very human readable?
  • Nwawel A Iroume
    Nwawel A Iroume over 8 years
    This work for a single class but not with related classes objets
  • gies0r
    gies0r about 7 years
    @NwawelAIroume: True. If you have an object which e.g. is containing multiple objects in a list the error is still is not JSON serializable
  • pattyd
    pattyd over 6 years
    @gies0r so, in that case how could you get the json of those multiple objects?
  • Bikash Gyawali
    Bikash Gyawali about 6 years
    Can someone provide a good reference for the approach of inheriting from JSONEncoder and then implementing the JSONEncoder.default() function?
  • Bikash Gyawali
    Bikash Gyawali about 6 years
    Also, what do I do if I have to get the JSON representation of not just 1 object but a list of objects?
  • H S Rathore
    H S Rathore over 5 years
    the __dict__ approach, Works like a charm, just the class name gets appended to the attribute names. Which in my case is ok. Using Python3.5 on Mac
  • Dakota Hawkins
    Dakota Hawkins over 5 years
    This worked for me, but because of datetime.date members I changed it slightly: default=lambda x: getattr(x, '__dict__', str(x))
  • codeman48
    codeman48 over 5 years
    @Dakota nice work-around; datetime.date is a C implementation hence it has no __dict__ attribute. IMHO for uniformity's sake, datetime.date should be having it...
  • Kyle Delaney
    Kyle Delaney about 5 years
    I get NameError: name 'serialize' is not defined. Any tips?
  • Fantastory
    Fantastory almost 5 years
    Very nice. Just for classes that have slots: try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
  • naught101
    naught101 about 3 years
    This leads to some weird outcomes, which are probably OK, just confusing: r = Response(200, 'blah'); r['a'] = 1; r.b = 2 results in r == {'status_code': 200, 'body': 'blah', 'a': 1} and r.__dict__ == {'b': 2}. I would love to know where dict actually stores it's keys and values.
  • Tom
    Tom almost 3 years
    Hey @GBGOLC, Thanks for that awesome piece of code! I've tried to implement it with json.loads and dumps since there aren't any files where I'm using the code. I'm struggling to decode the Vehicle class in my example, which refers to wheels. Here's how I've changed it. Help much appreciated.
  • Tom
    Tom almost 3 years
    Ok, I solved my own problem: since decode_ changes the instance it is passed, there was no return value to pass to a new instance. Your code was fine and only my way of calling decode_ needed changing. Thanks again for the great code!
  • SagiZiv
    SagiZiv over 2 years
    I love it. This is amazing implementation. I have just 1 question if I may, why did you use call method and not new method in the meta class? I ask, because the call method gets called every time a new instance is created, so the code tries to register the class for every new instance. Not a big deal, and as I wrote I love this implementation it seems really great :-)
  • Tarynn
    Tarynn about 2 years
    This is the correct answer. The class knows how to serialize itself. It is too bad that you can't just pass the method name directly to json.dumps
  • Tarynn
    Tarynn about 2 years
    The question isn't about serialization, it is about serializing to JSON. There are a lot of reasons for doing that and pickle would be a horrible tool for most of them.