1

I've searched SO's posts but couldn't figure out solution to my problem. I've seen bunch of SO posts which explains how to serialize python objects haven't found anything which explains how to serialize dictionary of list of python objects.

Here's the problem statement and sample use-case:

import json  
import collections    
from collections import defaultdict     

class A:
    def __init__(self, a1_list=[], a2_str=None):
        self.a1_list  = []
        self.a2_str = a2_str

class B:
     def __init__(self, list_of_A_objects=[], b2_str=None):
        self.list_of_A_objects = list_of_A_objects
        self.b2_str = b2_str

if __name__ == "__main__":
     a1 = A([1, 2, 3], '123')
     a2 = A([4, 5, 6], '456')
     b1 = B([a1, a2], '123-456')

     a3 = A([11, 22, 33], '112233')
     a4 = A([44, 55, 66], '445566')
     b2 = B([a3, a4], '112233-445566')

     dict_b = defaultdict(list)
     dict_b['b'].append(b1)
     dict_b['b'].append(b2)
     json.dumps(dict_b) 

Error is thrown at the last line json.dumps(dict_b) saying

TypeError: Object of type 'B' is not JSON serializable

EDIT:

I've tried pickle as @coldspeed has suggested in the comments, but I'd like to view the dumped JSON with a text editor. If I use pickle, I can't view it since it is dumped in binary format.

Am I doing anything incorrectly?

Saurabh Gokhale
  • 53,625
  • 36
  • 139
  • 164
  • 3
    `json` does not support python objects, try pickle? – cs95 Dec 18 '18 at 15:51
  • @coldspeed: Thanks for the suggestion. Do you have any sample example? – Saurabh Gokhale Dec 18 '18 at 15:52
  • See this post: https://stackoverflow.com/questions/8968884/python-serialization-why-pickle – cs95 Dec 18 '18 at 15:53
  • 1
    @coldspeed: Thanks. Apparently, some SO posts such as https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable and https://stackoverflow.com/questions/10252010/serializing-class-instance-to-json?rq=1 shows how to use `json` to serialize python objects to JSON string. – Saurabh Gokhale Dec 18 '18 at 15:57
  • also you can use yaml https://stackoverflow.com/questions/52235173/how-to-change-string-object-location-to-an-object/52235347#52235347 – Brown Bear Dec 18 '18 at 15:58
  • I should have been more specific - it is not supported out-of-box. You need to define custom encoders and decoders, which you haven't done yet. – cs95 Dec 18 '18 at 15:58
  • @coldspeed: Actually, with pickle dumping in binary format, I can't read the dumped JSON using any text editor. I would like to view the dumped json. Is there a way to achieve it using JSONEncoder? – Saurabh Gokhale Dec 18 '18 at 16:08
  • 1
    I'm not familiar with JSONEncoder, but I just now remembered having written an answer once to do something similar: https://stackoverflow.com/a/45834618/4909087 – cs95 Dec 18 '18 at 16:11

1 Answers1

2

You could define a method to dump you classe to json with json.dumps.

import json  
from collections import defaultdict

class A:
    def __init__(self, items=[], string=None):
        self.items = items
        self.string = string

    def toJSON(self):
        return json.dumps(self, default=lambda obj: obj.__dict__)#, sort_keys=True, indent=4)


if __name__ == "__main__":
    a1 = A([1, 2, 3], '123')
    a2 = A([4, 5, 6], '456')
    b1 = A([a1, a2], '123-456')
    print(b1.toJSON())
    # Result: {"items": [{"items": [1, 2, 3], "string": "123"}, {"items": [4, 5, 6], "string": "456"}], "string": "123-456"}

    a3 = A([11, 22, 33], '112233')
    a4 = A([44, 55, 66], '445566')
    b2 = A([a3, a4], '112233-445566')

    dict_b = defaultdict(list)
    dict_b['b'].append(b1.toJSON())
    dict_b['b'].append(b2.toJSON())
    print(json.dumps(dict_b))

Disadvantages You always will have a serialized version of your class. To avoid this side effect you can set a new method to turn back it to a dict with loads

def to_dict(self):
    return json.loads(self.toJSON())

And at defaultdict append object.to_dict

dict_b = defaultdict(list)
dict_b['b'].append(b1.to_dict())
dict_b['b'].append(b2.to_dict())
print(json.dumps(dict_b))
Mauro Baraldi
  • 6,346
  • 2
  • 32
  • 43