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

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

Простой класс:

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

Что я должен сделать, чтобы получить вывод:

json.dumps()

Без ошибки (FileItem instance at ... is not JSON serializable)

43 ответа

Решение

У вас есть представление об ожидаемом выходе? Например, это будет делать?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

В этом случае вы можете просто позвонить json.dumps(f.__dict__),

Если вы хотите более индивидуализированный вывод, то вам придется подкласс JSONEncoder и реализовать свою собственную сериализацию.

Для тривиального примера см. Ниже.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

Затем вы передаете этот класс в json.dumps() метод как cls kwarg:

json.dumps(cls=MyEncoder)

Если вы также хотите декодировать, вам придется предоставить object_hook к JSONDecoder учебный класс. Например,

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 

Вот простое решение для простой функции:

.toJSON() метод

Вместо сериализуемого класса JSON реализуйте метод сериализатора:

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

Так что вы просто вызываете его для сериализации:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

будет выводить:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}

Для более сложных классов вы можете рассмотреть инструмент jsonpickle:

jsonpickle - это библиотека Python для сериализации и десериализации сложных объектов Python в JSON и из него.

Стандартные библиотеки Python для кодирования Python в JSON, такие как json, simplejson и demjson в stdlib, могут обрабатывать только примитивы Python, имеющие прямой эквивалент JSON (например, dicts, списки, строки, целые числа и т. Д.). jsonpickle основывается на этих библиотеках и позволяет сериализовать более сложные структуры данных в JSON. jsonpickle легко настраивается и расширяется, что позволяет пользователю выбирать JSON-бэкэнд и добавлять дополнительные бэкэнды.

(ссылка на jsonpickle на PyPi)

Большинство ответов включают изменение вызова 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)  #No need to change anything here

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

Как упоминалось во многих других ответах, вы можете передать функцию в json.dumpsдля преобразования объектов, которые не являются одним из типов, поддерживаемых по умолчанию, в поддерживаемый тип. Удивительно, но ни один из них не упоминает простейший случай - использование встроенной функции. vars для преобразования объектов в dict, содержащий все их атрибуты:

json.dumps(obj, default=vars)

Если вам нужна более конкретная сериализация для определенных типов (например, исключение определенных атрибутов), вы можете использовать настраиваемую функцию или JSONEncoder как описано в других ответах.

Просто добавь to_json метод для вашего класса, как это:

def to_json(self):
  return self.message # or how you want it to be serialized

И добавьте этот код (из этого ответа), где-нибудь наверху всего:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

При импорте модуль json будет исправлен, поэтому JSONEncoder.default() автоматически проверяет наличие специального метода to_json() и использует его для кодирования объекта, если он найден.

Как сказал Онур, но на этот раз вам не нужно обновлять каждый json.dumps() в вашем проекте.

Настоящий ответ на вопрос "сделать _ Class _ сериализуемым"

_

TL; DR: копирование и вставка из варианта 2

  • Во-первых, к сожалению, нет: "официального" решения для Python не существует.
    • Под официальным решением я имею в виду, что (по состоянию на 2021 год) нет возможности добавить метод в класс (например, toJSON в JavaScript) или способ зарегистрировать свой класс в модуле json, который будет автоматически вызываться, когда json.dumps([1,2, your_obj])выполняется. Я не уверен, почему это не объясняется никакими ответами.
    • Наиболее близким официальным подходом, вероятно, является ответ Эндихасита, который заключается в наследовании словаря. Однако наследование из словаря не очень хорошо работает для многих пользовательских классов, таких как AdvancedDateTime или тензоры pytorch.
  • Однако есть обходной путь без предостережений:
    1. Мутировать json.dumps (влияет везде, даже на модули pip, которые импортируют json)
    2. Добавлять def __json__(self) метод к вашему классу

_

Вариант 1. Позволить модулю выполнить исправление

(расширенная + упакованная версия ответа Fancy John , спасибо @FancyJohn)
pip install json-fix

some_file_thats_imported_before_your_class_definitions.py

      # Step 1: patch json.dumps, only needs to run once per runtime
from json_fix import fix_it; fix_it()

your_class_definition.py

      # Step 2
class YOUR_CLASS:
    def __json__(self):
        # YOUR CUSTOM CODE HERE
        #    you probably just want to do:
        #        return self.__dict__
        return "a built-in object that is natually json-able"

_

Вариант 2: патчить json.dumps самостоятельно

Примечание : этот подход упрощен и не учитывает управление поведением json для внешних классов (массивы numpy, datetime, dataframes, тензоры и т. Д.), Что и обрабатывает модуль pip (в варианте 1).

some_file_thats_imported_before_your_class_definitions.py

      # Step: 1
# create the patch
from json import JSONEncoder
def wrapped_default(self, obj):
    return getattr(obj.__class__, "__json__", wrapped_default.default)(obj)
wrapped_default.default = JSONEncoder().default
   
# apply the patch
JSONEncoder.original_default = JSONEncoder.default
JSONEncoder.default = wrapped_default

your_class_definition.py

      # Step 2
class YOUR_CLASS:
    def __json__(self, **options):
        # YOUR CUSTOM CODE HERE
        #    you probably just want to do:
        #        return self.__dict__
        return "a built-in object that is natually json-able"

_

Все остальные ответы кажутся «Лучшие практики / подходы к сериализации настраиваемого объекта»

Мне нравится ответ Онура, но я бы добавил, чтобы toJSON() Метод для объектов для сериализации себя:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

Если вы используете Python3.5+, вы можете использовать jsons, Он преобразует ваш объект (и все его атрибуты рекурсивно) в диктовку.

import jsons

a_dict = jsons.dump(your_object)

Или, если вы хотите строку:

a_str = jsons.dumps(your_object)

Или если ваш класс реализован jsons.JsonSerializable:

a_dict = your_object.json

Другой вариант - обернуть дамп JSON в свой собственный класс:

import json

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

    def __repr__(self):
        return json.dumps(self.__dict__)

Или, что еще лучше, создание подкласса класса FileItem из JsonSerializable учебный класс:

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


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

Тестирование:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'

Я столкнулся с этой проблемой на днях и реализовал более общую версию Encoder для объектов 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"
}

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

      import json

json.dumps(your_object, default=lambda __o: __o.__dict__)

Конец!

То, что идет ниже, является тестом.

      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="john@doe.net", company=company)


json.dumps(user, default=lambda __o: __o.__dict__)

Выход:

      {
  "id": 1, 
  "name": "John Doe", 
  "email": "john@doe.net", 
  "company": {
    "id": 1, 
    "name": "Example Ltd"
  }
}
import simplejson

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

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', 'alice@mail.com')))

если использовать стандарт jsonВам нужно определить default функция

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

print(json.dumps(User('alice', 'alice@mail.com'), default=default))

json ограничено с точки зрения объектов, которые он может печатать, и jsonpickle (вам может понадобиться pip install jsonpickle) ограничен с точки зрения не может отступать текст. Если вы хотите проверить содержимое объекта, класс которого вы не можете изменить, я все равно не смог бы найти более прямой путь, чем:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

Обратите внимание, что они по-прежнему не могут печатать методы объекта.

Самый простой ответ

      class Object(dict):
    def __init__(self):
        pass

    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        self[key] = value

# test
obj = Object()
obj.name = "John"
obj.age = 25
obj.brothers = [ Object() ]
text = json.dumps(obj)

Теперь он дает вам результат, ничего не меняйте в json.dumps(...)

      '{"name": "John", "age": 25, "brothers": [{}]}'

Чтобы бросить еще один бревно в этот 11-летний пожар, мне нужно решение, отвечающее следующим критериям:

  • Позволяет сериализовать экземпляр класса FileItem, используя только
  • Позволяет экземплярам FileItem иметь свойства: fileItem.fname
  • Позволяет передавать экземпляры FileItem в любую библиотеку, которая будет сериализовать их, используя
  • Не требует передачи каких-либо других полей в json.dumps (как пользовательский сериализатор)

IE:

      fileItem = FileItem('filename.ext')
assert json.dumps(fileItem) == '{"fname": "filename.ext"}'
assert fileItem.fname == 'filename.ext'

Мое решение:

  • Унаследовать класс obj от
  • Сопоставьте каждое свойство объекта с нижележащим dict
      class FileItem(dict):
    def __init__(self, fname):
        self['fname'] = fname

    #fname property
    fname: str = property()
    @fname.getter
    def fname(self):
        return self['fname']

    @fname.setter
    def fname(self, value: str):
        self['fname'] = value

    #Repeat for other properties

Да, это несколько утомительно, если у вас много свойств, но это JSONSerializable, и он ведет себя как объект, и вы можете передать его любой библиотеке, которая будет json.dumps(obj) Это.

Этот класс может сделать свое дело, он конвертирует объект в стандартный JSON.

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

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

Serializer.serialize(my_object)

работает в python2.7 а также python3,

Вот мои 3 цента...
Это демонстрирует явную сериализацию JSON для древовидного объекта Python.
Примечание. Если вам действительно нужен какой-то подобный код, вы можете использовать витой класс FilePath.

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)

Харако дал довольно аккуратный ответ. Мне нужно было исправить некоторые мелочи, но это работает:

Код

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

Обратите внимание, что нам нужно два шага для загрузки. На данный момент, __python__ свойство не используется.

Насколько это распространено?

Используя метод AlJohri, я проверяю популярность подходов:

Сериализация (Python -> JSON):

  • to_json: 266 595 на 2018-06-27
  • toJSON: 96 307 на 2018-06-27
  • __json__: 8,504 на 2018-06-27
  • for_json: 6,937 на 2018-06-27

Десериализация (JSON -> Python):

  • from_json: 226 101 на 2018-06-27

Это хорошо сработало для меня:

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

а потом

class FileItem(JsonSerializable):
    ...

а также

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)

Ребята, зачем вы все так усложняете? Вот простой пример:

      #!/usr/bin/env python3

import json
from dataclasses import dataclass

@dataclass
class Person:
    first: str
    last: str
    age: int

    @property
    def __json__(self):
        return {
            "name": f"{self.first} {self.last}",
            "age": self.age
        }

john = Person("John", "Doe", 42)
print(json.dumps(john, indent=4, default=lambda x: x.__json__))

Таким образом, вы также можете сериализовать вложенные классы, как __json__возвращает объект Python, а не строку. Нет необходимости использовать JSONEncoder, как defaultпараметр с простой лямбдой тоже работает нормально.

я использовал @propertyвместо простой функции, так как это кажется более естественным и современным. @dataclassтакже просто пример, он работает и для «нормального» класса.

Kyle Delaney, поэтому я попытался использовать ответ /questions/43384801/kak-sdelat-klass-json-serializuemyim/43384808#43384808, а также улучшенную версию /questions/9232647/kodirovanie-vlozhennogo-obekta-python-v-json/9232660#9232660

для создания миксина "JSONAble".

Итак, чтобы сделать класс JSON сериализуемым, используйте "JSONAble" в качестве суперкласса и либо вызовите:

 instance.toJSON()

или

 instance.asJSON()

для двух предложенных методов. Вы также можете расширить класс JSONAble другими предлагаемыми здесь подходами.

Пример теста для модульного теста с семейством и личностью дает следующие результаты:

toJSOn():

{
    "members": {
        "Flintstone,Fred": {
            "firstName": "Fred",
            "lastName": "Flintstone"
        },
        "Flintstone,Wilma": {
            "firstName": "Wilma",
            "lastName": "Flintstone"
        }
    },
    "name": "The Flintstones"
}

asJSOn():

{'name': 'The Flintstones', 'members': {'Flintstone,Fred': {'firstName': 'Fred', 'lastName': 'Flintstone'}, 'Flintstone,Wilma': {'firstName': 'Wilma', 'lastName': 'Flintstone'}}}

Модульный тест с образцом семьи и человека

def testJsonAble(self):
        family=Family("The Flintstones")
        family.add(Person("Fred","Flintstone")) 
        family.add(Person("Wilma","Flintstone"))
        json1=family.toJSON()
        json2=family.asJSON()
        print(json1)
        print(json2)

class Family(JSONAble):
    def __init__(self,name):
        self.name=name
        self.members={}
    
    def add(self,person):
        self.members[person.lastName+","+person.firstName]=person

class Person(JSONAble):
    def __init__(self,firstName,lastName):
        self.firstName=firstName;
        self.lastName=lastName;

jsonable.py, определяющий миксин JSONAble

 '''
Created on 2020-09-03

@author: wf
'''
import json

class JSONAble(object):
    '''
    mixin to allow classes to be JSON serializable see
    https://stackru.com/questions/3768895/how-to-make-a-class-json-serializable
    '''

    def __init__(self):
        '''
        Constructor
        '''
    
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)
        
    def getValue(self,v):
        if (hasattr(v, "asJSON")):
            return v.asJSON()
        elif type(v) is dict:
            return self.reprDict(v)
        elif type(v) is list:
            vlist=[]
            for vitem in v:
                vlist.append(self.getValue(vitem))
            return vlist
        else:   
            return v
    
    def reprDict(self,srcDict):
        '''
        get my dict elements
        '''
        d = dict()
        for a, v in srcDict.items():
            d[a]=self.getValue(v)
        return d
    
    def asJSON(self):
        '''
        recursively return my dict elements
        '''
        return self.reprDict(self.__dict__)   

Если вы не против установить пакет для него, вы можете использовать json-tricks:

pip install json-tricks

После этого вам просто нужно импортировать dump(s) от json_tricks вместо JSON, и это обычно будет работать:

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

который даст

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

И это в основном все!


Это будет отлично работать в целом. Есть некоторые исключения, например, если в __new__или больше волшебства метакласса происходит.

Очевидно, что загрузка также работает (иначе какой смысл):

from json_tricks import loads
json_str = loads(json_str)

Это предполагает, что module_name.test_class.MyTestCls может быть импортирован и не изменился несовместимыми способами. Вы получите обратно экземпляр, а не какой-то словарь или что-то еще, и это должна быть копия, идентичная копии, которую вы сбросили.

Если вы хотите настроить сериализацию чего-либо (де), вы можете добавить специальные методы в ваш класс, например так:

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

который сериализует только часть параметров атрибутов, в качестве примера.

И в качестве бесплатного бонуса вы получаете (де) сериализацию массивов, даты и времени, упорядоченные карты, а также возможность добавлять комментарии в json.

Отказ от ответственности: я создал json_tricks, потому что у меня была та же проблема, что и у вас.

Jsonweb, кажется, лучшее решение для меня. Смотрите http://www.jsonweb.info/en/latest/

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'

Опираясь на Quinten Cabo"S ответ:

def sterilize(obj):
    """Make an object more ameniable to dumping as json
    """
    if type(obj) in (str, float, int, bool, type(None)):
        return obj
    elif isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    list_ret = []
    dict_ret = {}
    for a in dir(obj):
        if a == '__iter__' and callable(obj.__iter__):
            list_ret.extend([sterilize(v) for v in obj])
        elif a == '__dict__':
            dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
        elif a not in ['__doc__', '__module__']:
            aval = getattr(obj, a)
            if type(aval) in (str, float, int, bool, type(None)):
                dict_ret[a] = aval
            elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
                dict_ret[a] = sterilize(aval)
    if len(list_ret) == 0:
        if len(dict_ret) == 0:
            return repr(obj)
        return dict_ret
    else:
        if len(dict_ret) == 0:
            return list_ret
    return (list_ret, dict_ret)

Отличия

  1. Работает для любой итерации, а не только list а также tuple (работает для массивов NumPy и т. д.)
  2. Работает для динамических типов (тех, которые содержат __dict__).
  3. Включает собственные типы float а также None поэтому они не преобразуются в строку.
  4. Классы, у которых есть __dict__и участники будут в основном работать (если__dict__ и имена участников конфликтуют, вы получите только один - скорее всего, член)
  5. Классы, которые являются списками и имеют члены, будут выглядеть как кортеж из списка и словаря.
  6. Python3 (что isinstance()звонок может быть единственным, что нужно изменить)
class DObject(json.JSONEncoder):
    def delete_not_related_keys(self, _dict):
        for key in ["skipkeys", "ensure_ascii", "check_circular", "allow_nan", "sort_keys", "indent"]:
            try:
                del _dict[key]
            except:
                continue

    def default(self, o):
        if hasattr(o, '__dict__'):
            my_dict = o.__dict__.copy()
            self.delete_not_related_keys(my_dict)
            return my_dict
        else:
            return o

a = DObject()
a.name = 'abdul wahid'
b = DObject()
b.name = a

print(json.dumps(b, cls=DObject))

Я придумал собственное решение. Используйте этот метод, передайте любой документ (dict,list, ObjectId и т. Д.) Для сериализации.

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc

Я столкнулся с этой проблемой, когда попытался сохранить модель Peewee в PostgreSQL. JSONField,

После некоторой борьбы вот общее решение.

Ключом к моему решению является прохождение исходного кода Python и понимание того, что документация кода (описанная здесь) уже объясняет, как расширить существующий json.dumps поддерживать другие типы данных.

Предположим, у вас есть модель, которая содержит некоторые поля, которые нельзя сериализовать в JSON, и модель, которая содержит поле JSON, изначально выглядит так:

class SomeClass(Model):
    json_field = JSONField()

Просто определите обычай JSONEncoder как это:

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

А потом просто используйте его в своем JSONField как ниже:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

Ключ является default(self, obj) метод выше. Для каждого ... is not JSON serializable жалобу, которую вы получаете от Python, просто добавьте код для обработки типа unserializable-to-JSON (например, Enum или же datetime)

Например, вот как я поддерживаю класс, наследующий от Enum:

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

Наконец, с помощью кода, реализованного, как указано выше, вы можете просто преобразовать любые модели Peewee в объект с поддержкой JSON, как показано ниже:

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

Хотя приведенный выше код был (несколько) специфичен для Peewee, но я думаю:

  1. Это применимо к другим ORM (Django и т. Д.) В целом
  2. Кроме того, если вы поняли, как json.dumps работает, это решение также работает с Python (без ORM) в целом тоже

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

Мне больше всего понравился метод Lost Koder. Я столкнулся с проблемами при попытке сериализовать более сложные объекты, члены / методы которых не сериализуемы. Вот моя реализация, которая работает на большем количестве объектов:

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)
Другие вопросы по тегам