ChatGPT解决这个技术问题 Extra ChatGPT

如何使类 JSON 可序列化

如何使 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
不幸的是,答案似乎都回答了“我如何序列化一个类?”这个问题。而不是行动问题“我如何使一个类可序列化?”这些答案假设您自己进行序列化,而不是将对象传递给其他对其进行序列化的模块。
如果您使用的是 Python3.5+,则可以使用 jsons。它会将您的对象(以及递归的所有属性)转换为字典。 import jsons 请参阅下面的答案 - 效果很好
@KyleDelaney我真的希望我可以实现一种接口/魔术方法也可以实现可序列化。我想我必须实现一个 .to_dict() 函数或可以在对象上调用的东西,然后再将它传递给尝试序列化它的模块。
令人惊讶的是,11 年来没有一个回答可以回答这个问题。 OP 表示他想使用 json.dumps,但所有答案,包括获得的赏金,都涉及创建自定义编码器,这完全避开了问题的重点。
也就是说,这个问题现在是一个规范问题,所以它吸引的答案告诉初学者正确的事情是完全合理的。

O
Onur Yıldırım

这是一个简单功能的简单解决方案:

.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"
}

非常有限。如果你有一个 dict {"foo":"bar","baz":"bat"},它将很容易地序列化为 JSON。如果你有 {"foo":"bar","baz":MyObject()},那么你不能。理想的情况是嵌套对象递归地序列化为 JSON,而不是显式地序列化。
它仍然可以工作。您缺少 o.__dict___。试试你自己的例子:class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
这个解决方案是可逆的吗?即从json重构对象容易吗?
这不适用于 datetime.datetime 个实例。它引发以下错误:'datetime.datetime' object has no attribute '__dict__'
我一定遗漏了一些东西,但这似乎不起作用(即,json.dumps(me) 没有调用 ObjecttoJSON 方法。
m
mkrieger1

你对预期的输出有什么想法吗?例如,这会做吗?

>>> 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"}'

然后将该类作为 cls kwarg 传递给 json.dumps() 方法:

json.dumps(cls=MyEncoder)

如果您还想解码,则必须向 JSONDecoder 类提供自定义 object_hook。例如:

>>> 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>
>>> 

使用 __dict__ 并非在所有情况下都有效。如果在实例化对象后尚未设置属性,则 __dict__ 可能未完全填充。在上面的示例中,您没问题,但是如果您还想编码类属性,则这些属性不会列在 __dict__ 中,除非它们已在类的 __init__ 调用中或通过其他方式进行了修改在对象被实例化之后。
+1,但是用作对象挂钩的 from_json() 函数应该有一个 else: return json_object 语句,因此它也可以处理一般对象。
如果您在新样式类上使用 __slots__,@KrisHardy __dict__ 也不起作用。
您可以使用上述自定义 JSONEncoder 来创建自定义协议,例如检查 __json_serializable__ 方法是否存在并调用它以获取对象的 JSON 可序列化表示。这将与其他 Python 模式保持一致,例如 __getitem____str____eq____len__
__dict__ 也不会递归工作,例如,如果您的对象的属性是另一个对象。
m
mrnom

对于更复杂的类,您可以考虑使用工具 jsonpickle

jsonpickle 是一个 Python 库,用于将复杂的 Python 对象与 JSON 进行序列化和反序列化。用于将 Python 编码为 JSON 的标准 Python 库,例如 stdlib 的 json、simplejson 和 demjson,只能处理具有直接 JSON 等价物的 Python 原语(例如,dicts、lists、strings、ints 等)。 jsonpickle 建立在这些库之上,并允许将更复杂的数据结构序列化为 JSON。 jsonpickle 是高度可配置和可扩展的——允许用户选择 JSON 后端并添加额外的后端。

(link to jsonpickle on PyPi)


来自 C#,这是我所期待的。一个简单的班轮,没有弄乱类。
jsonpickle 很棒。它非常适合具有多个级别的类的巨大、复杂、凌乱的对象
是否有将其保存到文件的正确方法的示例?该文档仅显示如何编码和解码 jsonpickle 对象。此外,这无法解码包含熊猫数据帧的字典。
@user5359531 您可以使用 obj = jsonpickle.decode(file.read())file.write(jsonpickle.encode(obj))
这个对我有用!。这是我需要的。我只是想打印一个行为场景对象。
a
andyhasit

大多数答案涉及更改对 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

如果您的类只是基本数据表示,则此方法有效,对于更棘手的事情,您始终可以显式设置键。


这真的是一个很好的解决方案:) 我相信我的情况是这样。好处:您可以通过使用 init 使其成为一个类来传达对象的“形状”,它本质上是可序列化的,并且看起来可以解释为 repr。
虽然“点访问”仍然缺失:(
啊,这似乎工作!谢谢,不知道为什么这不是公认的答案。我完全同意更改 dumps 不是一个好的解决方案。顺便说一句,在大多数情况下,您可能希望将 dict 继承与委托一起使用,这意味着您将在您的类中拥有一些 dict 类型属性,然后您将将此属性作为参数作为初始化传递,例如 {4 }。
在我的用例中,我需要将“不可见”的数据存储到 json.dumps(),所以我使用了这种方法。 DictWithRider 类接收任意对象,将其存储为成员,并通过函数 get_rider_obj() 使其可访问,但不会将其传递给 dict.__init__()。因此,想要查看“隐藏”数据的应用程序部分可以调用 d.get_rider_obj() 但 json.dumps() 基本上看到的是一个空字典。正如@PascalVKooten 提到的,您无法使用点符号访问常规成员,但您可以访问函数。
这个解决方案有点老套——对于真正的生产质量解决方案,请将 json.dumps() 和 json.loads() 替换为 jsonpickle.encode() 和 jsonpickle.decode()。您将避免编写丑陋的样板代码,最重要的是,如果您能够腌制对象,您应该能够使用 jsonpickle 序列化它而无需样板代码(复杂的容器/对象将正常工作)。
u
user1587520

正如许多其他答案中提到的,您可以将一个函数传递给 json.dumps 以将不是默认支持的类型之一的对象转换为支持的类型。令人惊讶的是,他们都没有提到最简单的情况,即使用内置函数 vars 将对象转换为包含其所有属性的 dict:

json.dumps(obj, default=vars)

请注意,这仅涵盖基本情况,如果您需要对某些类型(例如排除某些属性或没有 __dict__ 属性的对象)进行更具体的序列化,则需要使用自定义函数或 JSONEncoder在其他答案中。


不清楚您所说的 default=vars 是什么意思,这是否意味着 vars 是默认序列化程序?如果不是:这并不能真正解决您无法影响 json.dumps 调用方式的情况。如果您只是将一个对象传递给一个库并且该库对该对象调用 json.dumps,那么如果该库不以这种方式使用 dumps,那么您实现 vars 并没有真正的帮助。从这个意义上说,它等同于自定义 JSONEncoder
您是对的,这只是自定义序列化程序的简单选择,并不能解决您描述的情况。如果我没看错的话,如果您无法控制 json.dumps 的调用方式,则无法解决此问题。
对于某些对象,这种方法会抛出 vars() argument must have __dict__ attribute
这可能是最好的解决方案,最少的干扰,最容易理解
感谢这一点,与内置正确定义的库一起使用非常简单。
C
Cadoiz

只需将 to_json 方法添加到您的类中,如下所示:

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

并将这段代码 (来自 this answer 添加到所有内容的顶部:

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() 方法,并在找到时使用它对对象进行编码。

Just like Onur said,但这次您不必更新项目中的每个 json.dumps()


非常感谢!这是唯一能让我做我想做的事情的答案:能够在不更改现有代码的情况下序列化对象。其他方法大多对我不起作用。对象是在第三方库中定义的,序列化代码也是第三方的。改变它们会很尴尬。用你的方法,我只需要做TheObject.to_json = my_serializer
这是正确的答案。我做了一个小改动:import json _fallback = json._default_encoder.default json._default_encoder.default = lambda obj: getattr(obj.__class__, "to_json", _fallback)(obj)
C
Community

我喜欢 Onur's answer,但会扩展为包含一个可选的 toJSON() 方法,以便对象自行序列化:

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

我真的很喜欢这个;但不是 try-catch 可能会做类似 if 'toJSON' in obj.__attrs__(): 的事情......以避免静默失败(如果 toJSON() 由于其他原因而失败,而不是它不存在)......可能导致的失败到数据损坏。
@thclark 据我了解,idomatic python 要求宽恕,而不是许可,因此 try-except 是正确的方法,但应该捕获正确的异常,在这种情况下是 AttributeError 。
@phil 现在年纪大了几岁,也更聪明了,我同意你的看法。
这确实应该明确地捕获 AttributeError
如果在 obj.toJSON() 中引发 AttributeError 会怎样?
u
user222758

另一种选择是将 JSON 转储包装在其自己的类中:

import json

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

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

或者,更好的是,从 JsonSerializable 类继承 FileItem 类:

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"}'

嗨,我不太喜欢这种“自定义编码器”方法,如果你能让你的类 json 可序列化会更好。我试了试,试了试,一无所获。有什么想法如何做到这一点。问题是 json 模块针对内置的 python 类型测试你的类,甚至说自定义类让你的编码器:)。可以伪造吗?所以我可以对我的班级做点什么,让它表现得像 json 模块的简单列表?我尝试了 subclasscheck 和 instancecheck 但没有。
@ADRENALIN 如果所有类属性值都是可序列化的并且您不介意黑客攻击,您可以从主要类型(可能是dict)继承。您还可以使用 jsonpickle 或 json_tricks 或其他东西代替标准编码器(仍然是自定义编码器,但不需要编写或调用)。前者腌制实例,后者将其存储为属性的字典,您可以通过实现 __json__encode__ / __json_decode__ 来更改它(披露:我做了最后一个)。
这不会使 json 类的对象可序列化。它只提供了一种方法来获取返回的 json 字符串(微不足道)。因此 json.dumps(f) 将失败。这不是被问到的。
S
SpaceCityCowboy88

如果您使用的是 Python3.5+,则可以使用 jsons。 (PyPi:https://pypi.org/project/jsons/)它将您的对象(及其所有属性递归)转换为字典。

import jsons

a_dict = jsons.dump(your_object)

或者如果你想要一个字符串:

a_str = jsons.dumps(your_object)

或者,如果您的班级实施了 jsons.JsonSerializable

a_dict = your_object.json

如果您能够使用 Python 3.7+,我发现将 python 类转换为 dicts 和 JSON 字符串(反之亦然)的最简洁的解决方案是将 jsons 库与 dataclasses 混合使用。到目前为止,对我很好!
这是一个外部库,未内置在标准 Python 安装中。
仅适用于具有 slot 属性的类
可以,但不需要使用插槽。仅当根据特定类的签名进行转储时,您才需要插槽。在即将发布的 1.1.0 版本中,情况也不再如此。
这个库在反序列化/序列化方面都非常慢,至少从个人测试来看是这样。我建议改为其他 ser 库。
n
np_6

前几天我遇到了这个问题,并为 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"
}

虽然这有点老了..我面临一些循环导入错误。因此,我没有在最后一行中使用 return obj,而是使用了 return super(ObjectEncoder, self).default(obj)。参考HERE
J
Jeff Hykin

真正的答案:让 Python 的 json 模块与你的类一起工作

又名,解决:json.dumps({ "thing": YOUR_CLASS() })

TLDR:复制粘贴下面的选项 1 或选项 2

解释:

是的,存在一个可靠的解决方案

不,没有 python“官方”解决方案 通过官方解决方案,我的意思是(截至 2022 年)无法向您的类添加方法(如 JavaScript 中的 toJSON)和/或无法将您的类注册到内置- 在 json 模块中。当执行 json.dumps([1,2, your_obj]) 之类的东西时,python 不会检查查找表或对象方法。我不确定为什么其他答案不能解释这一点最接近的官方方法可能是 andyhasit 的答案,即从字典继承。但是,对于许多自定义类(如 AdvancedDateTime 或 pytorch 张量)来说,从字典继承并不是很好。

通过官方解决方案,我的意思是(截至 2022 年)无法向您的类添加方法(如 JavaScript 中的 toJSON)和/或无法使用内置 json 模块注册您的类。当执行 json.dumps([1,2, your_obj]) 之类的东西时,python 不会检查查找表或对象方法。

我不确定为什么其他答案不能解释这一点

最接近的官方方法可能是 andyhasit 的答案,即从字典继承。但是,对于许多自定义类(如 AdvancedDateTime 或 pytorch 张量)来说,从字典继承并不是很好。

理想的解决方法是: Mutate json.dumps (影响任何地方,甚至是导入 json 的 pip 模块) 添加 def __json__(self) 方法到你的类

改变 json.dumps(影响任何地方,甚至是导入 json 的 pip 模块)

将 def __json__(self) 方法添加到您的类

选项 1:让模块进行修补

pip install json-fix
Fancy John's answer 的扩展 + 打包版本,谢谢 @FancyJohn)

your_class_definition.py

import json_fix

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 naturally json-able"

而已。示例用法:

from your_class_definition import YOUR_CLASS
import json

json.dumps([1,2, YOUR_CLASS()], indent=0)
# '[\n1,\n2,\n"a built-in object that is naturally json-able"\n]'

它是如何工作的?请参阅选项 2 自行完成。
注意:
要使 json.dumps 适用于 Numpy 数组、Pandas DataFrames 和其他第 3 方对象,请参阅 the Module(仅约 2 行代码,但需要解释) .

选项 2:自己修补 json.dumps

注意:这种方法被简化了,并且错过了控制外部类(numpy 数组、日期时间、数据帧、张量等)的 json 行为。

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"

_

所有其他答案似乎都是“序列化自定义对象的最佳实践/方法”

其中,已涵盖 here in the docs(搜索“复杂”以获取编码复数的示例)


j
jtlz2
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))

我通过使用 lambda json.dumps(User('alice', 'alice@mail.com'), default=lambda x: x.__dict__) 删除 _asdict 函数来简化这一点
M
Martlark

json 在可以打印的对象方面受到限制,而 jsonpickle(您可能需要 pip install jsonpickle)在不能缩进文本方面受到限制。如果您想检查无法更改其类的对象的内容,我仍然找不到比以下更直接的方法:

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

注意:他们仍然无法打印对象方法。


D
Dan Brough

这是我的 3 美分 ...
这演示了树状 python 对象的显式 json 序列化。
注意:如果你真的想要这样的代码,你可以使用 twisted 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)

L
Lost Koder

这个类可以解决问题,它将 object 转换为标准 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.7python3 工作。


我最喜欢这种方法。我在尝试序列化成员/方法不可序列化的更复杂对象时遇到了问题。这是适用于更多对象的我的实现: ``` 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 除了 TypeError: o.__dict__[k] = str(v) return o return json.dumps(check(obj).__dict__, indent=2) ```
r
rectangletangle
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)

doc:参数 default(obj) 是一个函数,应该返回 obj 的可序列化版本或引发 TypeError。默认的 default 只会引发 TypeError。
M
Martin Thoma

jaraco 给出了一个非常简洁的答案。我需要修复一些小问题,但这有效:

代码

# 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:2018 年 6 月 27 日 96,307

__json__:2018-06-27 上的 8,504

for_json:2018 年 6 月 27 日的 6,937

反序列化(JSON -> Python):

from_json: 226,101 于 2018 年 6 月 27 日


j
jmhostalet

这对我来说效果很好:

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))

M
Mark

如果您不介意为其安装软件包,可以使用 json-tricks

pip install json-tricks

之后,您只需从 json_tricks 而不是 json 导入 dump(s),它通常会起作用:

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

例如,它仅序列化部分属性参数。

作为免费奖励,您可以获得 numpy 数组、日期和时间、有序地图的(反)序列化,以及在 json 中包含注释的能力。

免责声明:我创建了 json_tricks,因为我遇到了和你一样的问题。


我刚刚测试了 json_tricks 并且它可以美化(在 2019 年)。
W
Wolfgang Fahl

Kyle Delaney's comment is correct 所以我尝试使用答案 https://stackoverflow.com/a/15538391/1497139 以及 https://stackoverflow.com/a/10254820/1497139 的改进版本

创建一个“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 mixin

 '''
Created on 2020-09-03

@author: wf
'''
import json

class JSONAble(object):
    '''
    mixin to allow classes to be JSON serializable see
    https://stackoverflow.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__)   

您会发现这些方法现已集成在 https://pypi.org/project/pylodstorage/ 上的 https://github.com/WolfgangFahl/pyLoDStorage 项目中


m
matthewlent

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"}'

它适用于嵌套对象吗?包括解码和编码
S
Sheikh Abdul Wahid
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))

m
mheyman

基于 Quinten Caboanswer

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)

区别是

适用于任何可迭代对象,而不仅仅是列表和元组(它适用于 NumPy 数组等)适用于动态类型(包含 __dict__ 的类型)。包括本机类型 float 和 None,因此它们不会转换为字符串。具有 __dict__ 和成员的类大部分都可以工作(如果 __dict__ 和成员名称冲突,您只会得到一个 - 可能是成员)作为列表并具有成员的类看起来像列表的元组和字典 Python3(即实例() 调用可能是唯一需要改变的东西)


W
Will Charlton

我最喜欢 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)

s
sivabudh

当我尝试将 Peewee 的模型存储到 PostgreSQL JSONField 中时遇到了这个问题。

经过一段时间的挣扎,这是一般的解决方案。

我的解决方案的关键是通过 Python 的源代码并意识到代码文档(描述 here)已经解释了如何扩展现有的 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) 方法。对于您从 Python 收到的每一个 ... is not JSON serializable 投诉,只需添加代码来处理 unserializable-to-JSON 类型(例如 Enumdatetime

例如,以下是我支持从 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,但我认为:

它通常适用于其他 ORM(Django 等)此外,如果您了解 json.dumps 的工作原理,该解决方案通常也适用于 Python(无 ORM)

有任何问题,请在评论区留言。谢谢!


A
Adi Degani

首先,我们需要使我们的对象符合 JSON 标准,因此我们可以使用标准 JSON 模块转储它。我是这样做的:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o

Q
Quinten Cabo

此函数使用递归遍历字典的每个部分,然后调用非内置类型的类的 repr() 方法。

def sterilize(obj):
    object_type = type(obj)
    if isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif object_type in (list, tuple):
        return [sterilize(v) for v in obj]
    elif object_type in (str, int, bool, float):
        return obj
    else:
        return obj.__repr__()

D
Daniel Flippance

要在这个 11 年的火灾中再扔一个日志,我想要一个满足以下标准的解决方案:

允许仅使用 json.dumps(obj) 序列化类 FileItem 的实例

允许 FileItem 实例具有属性:fileItem.fname

允许将 FileItem 实例提供给任何将使用 json.dumps(obj) 对其进行序列化的库

不需要将任何其他字段传递给 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)它的库。


N
NicoHood

为什么你们把事情搞得这么复杂?这是一个简单的例子:

#!/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,因为带有简单 lambda 的 default 参数也可以正常工作。

我使用 @property 而不是一个简单的函数,因为这感觉更自然和现代。 @dataclass 也只是一个示例,它也适用于“普通”类。


可能是因为您需要为每个类定义一个 __json__ 属性,这有时会很麻烦。此外,dataclasses 提供了 asdict,因此从技术上讲,您根本不需要 __json__ 属性。
当然可以,但是如果您想以不同的方式表示 json 怎么办?就像在这种情况下,我结合了名字和姓氏。 Thje asdict 不适用于嵌套元素,对吧?
嗯,在这种情况下,我建议将 first 和 last 作为 InitVar(仅限初始化)字段,并在 __post_init__ 构造函数中设置 name 字段。我认为这应该有望在这种情况下以差异格式表示 json。另外,我可能错了,但我相信 asdict 也适用于嵌套数据类。
但是,如果您稍后更改变量,这将不起作用。
嗯,据我所知,它应该。你能举例说明你的意思吗?
D
Dewsworld

我想出了自己的解决方案。使用此方法,传递任何文档(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