14

Как сделать класс сериализуемым в JSON

6

Как сделать класс Python сериализуемым?

class FileItem:
    def __init__(self, fname):
        self.fname = fname

Попытка сериализовать в JSON:

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

Кто-нибудь может помочь мне решить эту проблему? Я пытаюсь сериализовать экземпляр класса FileItem в формат JSON, но получаю ошибку, что объект данного типа не может быть сериализован. Как мне это исправить?

5 ответ(ов)

2

В большинстве ответов предложено изменить вызов json.dumps(), что не всегда возможно или желательно (например, это может происходить внутри компонента фреймворка).

Если вы хотите иметь возможность вызывать json.dumps(obj) без изменений, то простым решением будет наследование от dict:

class FileItem(dict):
    def __init__(self, fname):
        dict.__init__(self, fname=fname)

f = FileItem('tasks.txt')
json.dumps(f)  # Здесь ничего менять не нужно

Это работает, если ваш класс представляет собой простую структуру данных. Для более сложных ситуаций вы всегда можете явно задать ключи в вызове dict.__init__().

Это решение работает, потому что json.dumps() проверяет, является ли объект одним из нескольких известных типов с помощью довольно непитоничного isinstance(value, dict). Поэтому, если вы действительно не хотите наследоваться от dict, можно переопределить __class__ и некоторые другие методы для достижения аналогичного результата.

0

Вы можете обернуть процесс сериализации объекта в формате JSON в отдельный класс. Вот простой пример:

import json

class FileItem:
    def __init__(self, fname: str) -> None:
        self.fname = fname

    def __repr__(self) -> str:
        return json.dumps(self.__dict__)

Однако, еще более хорошим вариантом будет создание класса FileItem, который будет наследоваться от протокола JsonSerializable. Пример реализации:

import json
from typing import Protocol

class JsonSerializable(Protocol):
    def to_json(self) -> str:
        return json.dumps(self.__dict__)

    def __repr__(self) -> str:
        return self.to_json()

class FileItem(JsonSerializable):
    def __init__(self, fname: str) -> None:
        self.fname = fname

Давайте протестируем нашу реализацию:

>>> f = FileItem('/foo/bar')
>>> f.to_json()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # приведение к строке
'{"fname": "/foo/bar"}'

Таким образом, вы получите более чистую и гибкую архитектуру для обработки сериализации ваших объектов в формат JSON.

0

Я столкнулся с этой проблемой на днях и реализовал более общую версию кодировщика для объектов Python, который может обрабатывать вложенные объекты и наследуемые поля:

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

Пример:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y
        
    def f(self):
        print("f")

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print(json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True))

Результат:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}

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

0

Действительно простое решение в одну строку

import json

json.dumps(your_object, default=vars)

Конец!

Вот тестовое использование:

import json
from dataclasses import dataclass

@dataclass
class Company:
    id: int
    name: str

@dataclass
class User:
    id: int
    name: str
    email: str
    company: Company

company = Company(id=1, name="Example Ltd")
user = User(id=1, name="John Doe", email="[email protected]", company=company)

json.dumps(user, default=vars)

Вывод будет следующим:

{
  "id": 1, 
  "name": "John Doe", 
  "email": "[email protected]", 
  "company": {
    "id": 1, 
    "name": "Example Ltd"
  }
}
0

Ваша проблема связана с тем, что стандартный модуль json не знает, как сериализовать объекты вашего класса User в JSON. В примере вы используете библиотеку simplejson, которая может работать с объектами непосредственно, но стандартный json требует, чтобы вы предоставили ему способ преобразования объектов в сериализуемый формат.

Для этого нужно определить функцию default, которая будет отвечать за преобразование вашего объекта в словарь. Вот как это можно сделать:

import json

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))

Таким образом, когда вы вызываете json.dumps, если встречается объект, который не может быть сериализован по стандартным правилам, Python будет использовать вашу функцию default для его преобразования. В этой функции вы можете вызывать метод _asdict(), чтобы вернуть атрибуты объекта в виде словаря, который уже может быть сериализован в JSON.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь