Home > Net >  How to store/load list of python class with dictionary in JSON
How to store/load list of python class with dictionary in JSON

Time:05-31

Im trying to store/load a dictionary of name: class to/from JSON but its not storing the dictionary variable from the class, just the other ones.

My class has

class Test():
    a = ''
    b = 0.0
    c = {}

Ive tried using

class MyEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__

to encode it but __dict__ only returns the a and b variables. Im essentially doing

testDict = {}
test = Test()
testDict['a'] = test
test. a = 'asdf'
test.b = 1.4
test.c['qwert'] = 5
with open('test.json', 'w') as outfile:
   output = json.dump(testDict, outfile, indent=3, cls=MyEncoder)

and when I try exporting to JSON it just exports everything but the dictionary

{
   "a": {
      "a": "asdf",
      "b": 1.4
   }
}

What do I need to do to export all of each instance of the class in my dictionary?

CodePudding user response:

Interesting problem. From an initial glance, it looks like the issue here is that the initial value of c is scoped to the class Test, rather than the instance test itself.

You can kind of test this out by changing the assignment slightly, to test.c = {'qwert': 5}, instead of updating the dict object directly. For example:

import json


class Test():
    a = ''
    b = 0.0
    c = {}


class MyEncoder(json.JSONEncoder):
    def default(self, o):
        return o.__dict__

testDict = {}
test = Test()
testDict['a'] = test
test. a = 'asdf'
test.b = 1.4
test.c = {'qwert': 5}

with open('test.json', 'w') as outfile:
   output = json.dump(testDict, outfile, indent=3, cls=MyEncoder)

The long-term solution would likely be to refactor code to use something like dataclasses instead:

import json
from dataclasses import dataclass, field, asdict


@dataclass
class Test:
    a: str = ''
    b: float = 0.0
    c: dict = field(default_factory=dict)


class MyEncoder(json.JSONEncoder):
    def default(self, o):
        return asdict(o)


testDict = {}
test = Test(a='asdf', b=1.4)
testDict['a'] = test
test.c['qwert'] = 5

with open('test.json', 'w') as outfile:
    output = json.dump(testDict, outfile, indent=3, cls=MyEncoder)

CodePudding user response:

What you have encountered is a difference between mutable and immutable objects a difference between instance attribute and class attribute.

This code:

class Test():
    a = ''
    b = 0.0
    c = {}

Creates a class (type) object with class attributes a='', b=0.0 and c={}.

Now when you create an instance:

test = Test()

It has no instance attributes, ie. test.__dict__ is empty.

When you assign

test.a = 'asdf'
test.b = 1.4

You create an instance attributes test.a and test.b, thus __dict__ now contain those.

However when you do this:

test.c['qwert'] = 5

You are only referencing class attributes, instead of creating a new dictionary.

Therefore what you end up with is:

Test.__dict__ # -> {'a': '', 'b': 0.0, 'c': {'qwerty': 5}} # class object __dict__
test.__dict__ # -> {'a': 'asdf', 'b': 1.4} # instance __dict__

Afterward when you reference o.__dict__, 'c' is simply not there.

You can use the constructor to ensure instance attributes are always set:

class Test():

    def __init__(self):
        a = ''
        b = 0.0
        self.c = {}

or dataclasses, they will create a constructor for you:

from dataclasses import dataclass
from typing import Any, Dict


@dataclass
class Test:
    a: str
    b: float
    c: Dict[str, Any]

For my own purposes, I use pydantic, to avoid recreating the same pattern every time I create a class. Pydantic also validates the field values during assignment.

from typing import Any, Dict
from pydantic import BaseModel


class Test(BaseModel):
    a: str
    b: float
    c: Dict[str, Any]
  • Related