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]