LBes
LBes

Reputation: 3456

Nested classes are not serializable in python when trying JSON dump

I currently have two classes in Python like these ones

class person:
    age=""
    name=""
    ranking = {}

    def addRanking():
    #Do Whatever treatment and add to the ranking dict

class ranking:
    semester = ""
    position = ""
    gpa = ""

I have my list of person as a dictionary called dictP json.dumps() this dictionary but it seems that it doesn't work. Here is my function to dump to JSON

def toJson():
    jsonfile = open('dict.json', 'w')
    print(json.dump(listP, jsonfile))

I get the famous: is not JSON serializable.

Would you know what I can do to help this problem. I thought that having two dictionaries (which are serializable) would avoid this kind of issue, but apparently not.

Thanks in advance

Edit:

Here is an example (typed on my phone sorry for typos, I'm not sure it does run but it's so you get the idea):

class person:
    age=""
    name=""
    ranking = {}

    def __init__(self, age, name):
        self.age = age
        self.name = name
        self.ranking = {}

    def addRanking(self,semester,position,gpa):
        #if the semester is not already present in the data for that person
            self.ranking[semester] = make_ranking(semester,position,gpa)

class ranking:
    semester = ""
    position = ""
    gpa = ""

    def __init__(self, semester, position, gpa):
        self.semester = semester
        self.position = position
        self.gpa = gpa

dictP = {}


def make_person(age, name):
    # Some stuff happens there
    return person(age,name)

def make_ranking(semester,postion,gpa):
    #some computation there
    return ranking(semester,position,gpa)

def pretending_to_read_csv():
    age = 12
    name = "Alice"
    p = make_person(age, name)
    dictP["1"] = p

    age = 13
    name = "Alice"
    p = make_person(age, name)
    dictP["2"] = p

    #We read a csv for ranking that gives us an ID 
    semester = 1
    position = 4 
    gpa = 3.2
    id = 1 

    dictP["1"].addRanking(semester, position, gpa)

    semester = 2
    position = 4 
    gpa = 3.2
    id = 1 

    dictP["1"].addRanking(semester, position, gpa)

Upvotes: 0

Views: 2281

Answers (2)

chris
chris

Reputation: 2063

For a dictionary to be serializable, note that all the keys & values in that dictionary must be serializable as well. You did not show us what listP contains, but I'm guessing it's something like this:

>>> listP
[<__main__.person instance at 0x107b65290>, <__main__.person instance at 0x107b65368>]

Python instances are not serializable.

I think you want a list of dictionaries, which would look like this:

>>> listP
[{'ranking': {}, 'age': 10, 'name': 'fred'}, {'ranking': {}, 'age': 20, 'name': 'mary'}]

This would serialize as you expect:

>>> import json
>>> json.dumps(listP)
'[{"ranking": {}, "age": 10, "name": "fred"}, {"ranking": {}, "age": 20, "name": "mary"}]'

UPDATE

(Thanks for adding example code.)

>>> pretending_to_read_csv()
>>> dictP
{'1': <__main__.person instance at 0x107b65368>, '2': <__main__.person instance at 0x107b863b0>}

Recall that user-defined classes cannot be serialized automatically. It's possible to extend the JSONEncoder directly to handle these cases, but all you really need is a function that can turn your object into a dictionary comprised entirely of primitives.

def convert_ranking(ranking):
    return {
        "semester": ranking.semester,
        "position": ranking.position,
        "gpa": ranking.gpa}

def convert_person(person):
    return {
        "age": person.age,
        "name": person.name,
        "ranking": {semester: convert_ranking(ranking) for semester, ranking in person.ranking.iteritems()}}

One more dictionary comprehension to actually do the conversion and you're all set:

>>> new_dict = {person_id: convert_person(person) for person_id, person in dictP.iteritems()}
>>> from pprint import pprint
>>> pprint(new_dict)
{'1': {'age': 12,
       'name': 'Alice',
       'ranking': {1: {'gpa': 3.2, 'position': 4, 'semester': 1},
                   2: {'gpa': 3.2, 'position': 4, 'semester': 2}}},
 '2': {'age': 13, 'name': 'Alice', 'ranking': {}}}

Since no user-defined objects are stuffed in there, this will serialize as you hope:

>>> json.dumps(new_dict)
'{"1": {"ranking": {"1": {"position": 4, "semester": 1, "gpa": 3.2}, "2": {"position": 4, "semester": 2, "gpa": 3.2}}, "age": 12, "name": "Alice"}, "2": {"ranking": {}, "age": 13, "name": "Alice"}}'

Upvotes: 1

Andrew Delgadillo
Andrew Delgadillo

Reputation: 161

You can try calling json.dump on the .__dict__ member of your instance. You say that you have a list of person instances so try doing something like this:

listJSON = []
for p in listP
    #append the value of the dictionary containing data about your person instance to a list
    listJSON.append(p.__dict__)
json.dump(listJSON, jsonfile)

If you are storing your person instances in a dictionary like so: dictP = {'person1': p1, 'person2': p2} this solution will loop through the keys and change their corresponding values to the __dict__ member of the instance:

for key in dictP:
    dictP[key] = dictP[key].__dict__
json.dump(dictP, jsonfile)

Upvotes: 1

Related Questions