6

I am trying to serialize to JSON the __dict__ of an object, which is working fine, until I append objects to one of the instance attribute of my first object:

from json import dumps

class A(object):
    def __init__(self):
        self.b_list = []

class B(object):
    def __init__(self):
        self.x = 'X'
        self.y = 'Y'
    def __repr__(self):
        return dumps(self.__dict__)

a = A()

print dumps(a.__dict__)  # works fine

a.b_list.append(B())

print dumps(a.__dict__)

When calling for the second time dumps, I got the following TypeError:

TypeError: {"y": "Y", "x": "X"} is not JSON serializable

I don't understand why I keep getting this error while I can't see why this is not serializable to JSON.

evuez
  • 3,257
  • 4
  • 29
  • 44

1 Answers1

7

That's because instances of B are not a simple type. Because you gave B a __repr__ method, the instance is printed as it's JSON representation, but it is not itself a supported JSON type.

Remove the __repr__ method and the traceback is much less confusing:

>>> class A(object):
...     def __init__(self):
...         self.b_list = []
... 
>>> class B(object):
...     def __init__(self):
...         self.x = 'X'
...         self.y = 'Y'
... 
>>> a = A()
>>> a.b_list.append(B())
>>> 
>>> print dumps(a.__dict__)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mj/Development/Library/buildout.python/parts/opt/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/Users/mj/Development/Library/buildout.python/parts/opt/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/mj/Development/Library/buildout.python/parts/opt/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/Users/mj/Development/Library/buildout.python/parts/opt/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <__main__.B object at 0x10a753e10> is not JSON serializable

Use the default keyword argument to encode custom objects:

def encode_b(obj):
    if isinstance(obj, B):
        return obj.__dict__
    return obj

json.dumps(a, default=encode_b)

Demo:

>>> def encode_b(obj):
...     if isinstance(obj, B):
...         return obj.__dict__
...     return obj
... 
>>> dumps(a.__dict__, default=encode_b)
'{"b_list": [{"y": "Y", "x": "X"}]}'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • thanks for the explanation and the demo! I thought json.dumps would use `__repr__` to serialize the objects, but without `__repr__` it is indeed much more clear. – evuez Nov 19 '13 at 18:22
  • That doesn't make sense to me: if B's repr returns a string, surely that's a simple type? This does not clear up the understanding of json encoding repr :( – Tjorriemorrie Jul 23 '15 at 09:56
  • 1
    @Tjorriemorrie: JSON doesn't use `__repr__`. The OP tried to make instances of `B` serialisable to JSON by giving them a `__repr__` method, but that method isn't used here at all. – Martijn Pieters Jul 23 '15 at 09:58
  • Is there a way to add a decorator to an object that will then handle the `encode` logic/process automatically when it gets encoded instead of extending the JsonEncoder? (I assume there is no such attribute that json looks for on an object?) – Tjorriemorrie Jul 23 '15 at 10:02
  • @Tjorriemorrie: You could use a decorator but you'll *still* have to extend `JSONEncoder`, because indeed there is no attribute that the encoder looks for. The Pyramid project extended their encoder to look for `__json__` methods, but again, it requires a custom encoder. – Martijn Pieters Jul 23 '15 at 11:04
  • OK, thanks, think I got it now. Using [this as my solution](http://stackoverflow.com/questions/5022066/how-to-serialize-sqlalchemy-result-to-json#answer-31569287) then. – Tjorriemorrie Jul 23 '15 at 13:26