Сделайте так, чтобы кодировщик Python json поддерживал новые классы данных Python

Начиная с Python 3.7 существует так называемый класс данных:

from dataclasses import dataclass

@dataclass
class Foo:
    x: str

Однако следующее не удается:

>>> import json
>>> foo = Foo(x="bar")
>>> json.dumps(foo)
TypeError: Object of type Foo is not JSON serializable

Как я могу заставить json.dumps() кодировать экземпляры Foo в объекты json?


person miracle2k    schedule 11.07.2018    source источник


Ответы (7)


Подобно тому, как вы можете добавить поддержку кодировщика JSON для datetime объектов или десятичных знаков, вы также можете предоставить собственный подкласс кодировщика для сериализации классов данных:

import dataclasses, json

class EnhancedJSONEncoder(json.JSONEncoder):
        def default(self, o):
            if dataclasses.is_dataclass(o):
                return dataclasses.asdict(o)
            return super().default(o)

json.dumps(foo, cls=EnhancedJSONEncoder)
person miracle2k    schedule 11.07.2018
comment
Важно отметить, что для класса данных с именем Foo и экземпляра foo_instance = Foo(...) оба dataclasses.is_dataclass(Foo) и dataclasses.is_dataclass(foo_instance) оцениваются как True, что приводит к TypeError для dataclasses.asdict(o), если o является самим классом данных, а не его экземпляром. - person Jan; 04.02.2020
comment
Как насчет вложенности - например, сериализация d={ 'a': Foo(1,2), 'b': Foo(3,4)} - person WestCoastProjects; 02.04.2020
comment
Как с этим справиться с объектом типа ndarray? - person H.H; 13.07.2020

Разве вы не можете просто использовать функцию dataclasses.asdict() для преобразования класса данных в dict? Что-то типа:

>>> @dataclass
... class Foo:
...     a: int
...     b: int
...     
>>> x = Foo(1,2)
>>> json.dumps(dataclasses.asdict(x))
'{"a": 1, "b": 2}'
person Chuck Wooters    schedule 10.01.2019
comment
Класс данных может быть глубоко вложенной частью большой структуры. Используя собственный кодировщик, вы можете делать json.dumps({"obj": [something_that_may_or_may_not_contain_a_dataclass]}) как - person miracle2k; 10.01.2019
comment
asdict () будет правильно обрабатывать все вложенные классы данных, в результате мы получаем вложенные словари, которые обычно загружаются в строку jison (но! например, тип datetime должен обрабатываться дополнительно перед загрузкой в ​​строку) - person Evgeniy_Burdin; 05.06.2020

Способы получения JSONified экземпляра класса данных

Есть несколько вариантов для достижения этой цели, выбор каждого из которых подразумевает анализ того, какой подход лучше всего подходит для ваших нужд:

Стандартная библиотека: dataclass.asdict

import dataclasses
import json


@dataclass.dataclass
class Foo:
    x: str

foo = Foo(x='1')
json_foo = json.dumps(dataclasses.asdict(foo)) # '{"x": "1"}'

Вернуть его обратно в экземпляр класса данных нетривиально, поэтому вы можете посетить этот ответ https://stackoverflow.com/a/53498623/2067976

Marshmallow Dataclass

from dataclasses import field
from marshmallow_dataclass import dataclass


@dataclass
class Foo:
    x: int = field(metadata={"required": True})

foo = Foo(x='1') # Foo(x='1')
json_foo = foo.Schema().dumps(foo) # '{"x": "1"}'

# Back to class instance.
Foo.Schema().loads(json_foo) # Foo(x=1)

В качестве бонуса для marshmallow_dataclass вы можете использовать проверку самого поля, эта проверка будет использоваться, когда кто-то десериализует объект из json, используя эту схему.

классы данных Json

from dataclasses import dataclass
from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: int

foo = Foo(x='1')
json_foo = foo.to_json() # Foo(x='1')
# Back to class instance
Foo.from_json(json_foo) # Foo(x='1')

Кроме того, в дополнение к этому примечанию, класс данных marshmallow выполнял преобразование типов за вас, тогда как dataclassses-json (ver .: 0.5.1) игнорирует это.

Написать собственный кодировщик

Следуйте принятому ответу miracle2k и повторно используйте пользовательский кодировщик json.

person Andriy Ivaneyko    schedule 03.07.2020
comment
Спасибо за marshmallow_dataclass, это действительно хороший способ получить реальный объект из JSON с проверкой (и даже YAML). - person mickours; 22.09.2020
comment
@mickours, добро пожаловать :) кстати, я не упоминал, что он также будет работать на python36 (под капотом есть backport dataclass) - person Andriy Ivaneyko; 22.09.2020

Если вы согласны с использованием библиотеки для этого, вы можете использовать dataclasses-json. Вот пример:

from dataclasses import dataclass

from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: str


foo = Foo(x="some-string")
foo_json = foo.to_json()

Он также поддерживает встроенные классы данных - если в вашем классе данных есть поле, типизированное как другой класс данных - если все включенные классы данных имеют декоратор @dataclass_json.

person Jundiaius    schedule 10.01.2020
comment
Я пробовал это со встроенным классом данных, и он не работает - person WestCoastProjects; 02.04.2020

Я бы предложил создать родительский класс для ваших классов данных с помощью метода to_json():

import json
from dataclasses import dataclass, asdict

@dataclass
class Dataclass:
    def to_json(self) -> str:
        return json.dumps(asdict(self))

@dataclass
class YourDataclass(Dataclass):
    a: int
    b: int

x = YourDataclass(a=1, b=2)
x.to_json()  # '{"a": 1, "b": 2}'

Это особенно полезно, если у вас есть другие функции для добавления ко всем вашим классам данных.

person Phil Filippak    schedule 30.12.2020


Итак, вот что я сделал, когда был в подобной ситуации.

  1. Создайте настраиваемую фабрику словарей, которая преобразует вложенные классы данных в словарь.

    def myfactory (data): return dict (x вместо x в данных, если x [1] не равно None)

  2. Если foo - ваш @dataclass, просто укажите фабрику словарей для использования метода myfactory ():

    fooDict = asdict (foo, dict_factory = myfactory)

  3. Преобразование fooDict в json

    fooJson = json.dumps (fooDict)

Это должно работать !!

person Vaibs007    schedule 29.07.2021