Convert dataclass of dataclass to json string

Question:

I have a json string that I want to read, convert it to an object that I can manipulate, and then convert it back into a json string.

I am utilizing the python 3.10 dataclass, and one of the attributes of the class is another class (mySubClass). When I call json.loads(myClass), I get the following error: TypeError: Object of type mySubClass is not JSON serializable.

Is there a way I can instantiate the dataclass myClass with everything it needs (including mySubClass), and then have a "post init operation" that will convert myClass.mySubClass into a simple json str? Or am I going about this the wrong way?

My original goal was to have the following:

import json
from dataclasses import dataclass

@dataclass
mySubClass:
  sub_item1: str
  sub_item2: str

@dataclass
myClass:
  item1: str
  item2: mySubClass()

...
convert_new_jsonStr_toObj = json.loads(received_json_str, object_hook=lambda d: SimpleNamespace(**d))

...
#: Get new values/do "stuff" to the received json string

myClass_to_jsonStr = json.dumps(myClass(item1=convert_new_jsonStr_toObj.item1, item2=mySubClass(sub_item1=convert_new_jsonStr_toObj.sub_item1, sub_item2=convert_new_jsonStr_toObj.sub_item2)))

...
#: Final json will look something like:

processed_json_str = "{
   "item1" : "new_item1",
   "item2" : {
         "sub_item1": "new_sub_item1",
         "sub_item2": "new_sub_item2"
    }"
}
#: send processed_json_str back out...

#: Note: "processed_json_str" has the same structure as "received_json_str".

Asked By: Alex

||

Answers:

If I’ve understood your question correctly, you can do something like this::

import json
import dataclasses

@dataclasses.dataclass
class mySubClass:
  sub_item1: str
  sub_item2: str

@dataclasses.dataclass
class myClass:
  item1: str
  item2: mySubClass

  # We need a __post_init__ method here because otherwise
  # item2 will contain a python dictionary, rather than
  # an instance of mySubClass.
  def __post_init__(self):
      self.item2 = mySubClass(**self.item2)


sampleData = '''
{
  "item1": "This is a test",
  "item2": {
    "sub_item1": "foo",
    "sub_item2": "bar"
  }
}
'''

myvar = myClass(**json.loads(sampleData))
myvar.item2.sub_item1 = 'modified'
print(json.dumps(dataclasses.asdict(myvar)))

Running this produces:

{"item1": "This is a test", "item2": {"sub_item1": "modified", "sub_item2": "bar"}}

As a side note, this all becomes easier if you use a more fully featured package like pydantic:

import json
from pydantic import BaseModel

class mySubClass(BaseModel):
  sub_item1: str
  sub_item2: str

class myClass(BaseModel):
  item1: str
  item2: mySubClass

sampleData = '''
{
  "item1": "This is a test",
  "item2": {
    "sub_item1": "foo",
    "sub_item2": "bar"
  }
}
'''

myvar = myClass(**json.loads(sampleData))
myvar.item2.sub_item1 = 'modified'
print(myvar.json())
Answered By: larsks

Without using any libraries other than the builtins:

import dataclasses
import json


@dataclasses.dataclass
class mySubClass:
    sub_item1: str
    sub_item2: str


@dataclasses.dataclass
class myClass:
    item1: str
    item2: mySubClass

    @classmethod
    def from_json(cls, string: str):
        data: dict = json.loads(string)
        if isinstance(data['item2'], dict):
            data['item2'] = mySubClass(**data['item2'])
        return cls(**data)

    def json(self):
        return json.dumps(self, default=lambda o: o.__dict__)


sampleData = '''
{
  "item1": "This is a test",
  "item2": {
    "sub_item1": "foo",
    "sub_item2": "bar"
  }
}
'''

myvar = myClass.from_json(sampleData)
myvar.item2.sub_item1 = 'modified'
print(myvar.json())

Which becomes a bit easier, using a ser/de library like dataclass-wizard, or dataclasses-json:

import dataclasses

from dataclass_wizard import JSONWizard


@dataclasses.dataclass
class myClass(JSONWizard):
    item1: str
    item2: 'mySubClass'

    # optional
    @property
    def json(self, indent=None):
        return self.to_json(indent=indent)


@dataclasses.dataclass
class mySubClass:
    sub_item1: str
    sub_item2: str


sampleData = '''
{
  "item1": "This is a test",
  "item2": {
    "sub_item1": "foo",
    "sub_item2": "bar"
  }
}
'''

c = myClass.from_json(sampleData)
print(c.json)

Disclaimer: I am the creator and maintenor of this library.

Answered By: rv.kvetch

orjson is very fast, correct Python JSON library supporting dataclasses, datetimes, and numpy

https://github.com/ijl/orjson

Like so

enter image description here

Performance:

https://github.com/ijl/orjson#performance

Answered By: Piotr