2

I have a list of objects in my Python code. Each object is of type Outer with associated Inner objects - the classes are defined like this:

from decimal import Decimal

@dataclass
class Inner:
    col_a: Decimal
    col_b: str
    col_c: List['str']

@dataclass
class Outer:
    col_a: str
    col_b: Decimal
    col_c: List[Inner]

I would like to convert these objects into JSON. As I am using Decimal, I was hoping to just create my own encoder and use it in conjunction with json.dumps():

class DecimalJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return str(obj)
        else:
            return super(DecimalJsonEncoder, self).default(obj)

... and...

my_json = json.dumps(my_list, cls=DecimalJsonEncoder)

However, when I create a list of Outer objects (my_list) and try to create JSON, I get this error:

TypeError: Object of type Outer is not JSON serializable

I'm using Python 3.7.

Thanks in advance for any assistance.

GarlicBread
  • 1,671
  • 8
  • 26
  • 49

2 Answers2

3

You wish the encoder to support both Decimal and dataclasses. You can do it like so:

import dataclasses, json

class JSONEncoder(json.JSONEncoder):
        def default(self, o):
            if dataclasses.is_dataclass(o):
                return dataclasses.asdict(o)
            if isinstance(obj, Decimal):
                return str(obj)
            return super().default(o)

json.dumps(foo, cls=JSONEncoder)
Bharel
  • 23,672
  • 5
  • 40
  • 80
0

Just adding an alternate solution that can work for you. This can be especially useful if you need to de-serialize (load) JSON data back to the nested dataclass model.

This uses an external library dataclass-wizard, which is a JSON serialization framework built on top of dataclasses. The example below should work for Python 3.7+ with the included __future__ import.

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from dataclass_wizard import JSONWizard


@dataclass
class Outer(JSONWizard, str=False):
    col_a: str
    col_b: Decimal
    col_c: list[Inner]


@dataclass
class Inner:
    col_a: Decimal
    col_b: str
    col_c: list[str]


def main():
    obj = Outer(col_a='abc',
                col_b=Decimal('1.23'),
                col_c=[Inner(col_a=Decimal('3.21'),
                             col_b='xyz',
                             col_c=['blah', '1111'])])
    
    print(obj)
    
    print(obj.to_json())
    # {"colA": "abc", "colB": "1.23", "colC": [{"colA": "3.21", "colB": "xyz", "colC": ["blah", 1111]}]}
    
    # assert we get the same object when de-serializing the string data
    assert obj == obj.from_dict(obj.to_dict())


if __name__ == '__main__':
    main()

Output:

Outer(col_a='abc', col_b=Decimal('1.23'), col_c=[Inner(col_a=Decimal('3.21'), col_b='xyz', col_c=['blah', '1111'])])
{"colA": "abc", "colB": "1.23", "colC": [{"colA": "3.21", "colB": "xyz", "colC": ["blah", "1111"]}]}
rv.kvetch
  • 9,940
  • 3
  • 24
  • 53