为什么 JSONEncoder 不适用于命名元组?

Rin*_*nku 3 python json data-structures

我无法转储collections.namedtuple为正确的 JSON。

首先,考虑使用自定义 JSON 序列化程序的官方示例:

import json

class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return [obj.real, obj.imag]
        # Let the base class default method raise the TypeError
        return json.JSONEncoder.default(self, obj)

json.dumps(2 + 1j, cls=ComplexEncoder)   # works great, without a doubt
Run Code Online (Sandbox Code Playgroud)

其次,现在考虑以下示例,它告诉 Python 如何对Friend对象进行 JSONize:

import json

class Friend():
    """ struct-like, for storing state details of a friend """
    def __init__(self, _id, f_name, l_name):
        self._id = _id
        self.f_name = f_name
        self.l_name = l_name

t = Friend(21, 'Steve', 'Rogerson')

class FriendEncoder(json.JSONEncoder):
    """ take a Friend object and make it truly json """
    def default(self, aFriend):
        if isinstance(aFriend, Friend):
            return {
                "id": aFriend._id,
                "f_name": aFriend.f_name,
                "l_name": aFriend.l_name,
            }
        return super(FriendEncoder, self).default(aFriend)

json.dumps(t, cls=FriendEncoder) # returns correctly JSONized string
Run Code Online (Sandbox Code Playgroud)

最后,当我们尝试使用 namedtuples 实现同样的事情时,json.dumps(t, cls=FriendEncoder)不会给出任何错误,但会给出错误的输出。看一看:

import pdb
import json
from collections import namedtuple

Friend = namedtuple("Friend", ["id", 'f_name', 'l_name'])

t = Friend(21, 'Steve', 'Rogerson')

print(t)

class FriendEncoder(json.JSONEncoder):
    """ take a Friend collections.namedtuple object and make it truly json """
    def default(self, obj):
        if True:    # if isinstance(obj, Friend):
            ans = dict(obj._asdict())
            pdb.set_trace()     # WOW!! even after commenting out the if and hardcoding True, debugger doesn't get called
            return ans
        return json.JSONEncoder.default(self, obj)

json.dumps(t, cls=FriendEncoder)
Run Code Online (Sandbox Code Playgroud)

我得到的输出不是类似 dict 的,而只是一个值列表,即 [21, 'Steve', 'Rogerson']

为什么?

默认行为是否会导致信息丢失?
json.dumps 是否忽略显式传递的编码器?


编辑:通过正确 jsonized namedtuple 我的意思是 json.dumps 应该返回完全 一样的数据dict(nt._asdict()),其中nt是预定义的 namedtuple

mar*_*eau 6

正如我在评论中所说,json.JSONEncoder唯一的调用是default在遇到对象类型时它还不知道如何序列化自身。有一个他们的表json的文档。这是它的屏幕截图以供参考:

默认支持的类型表

请注意,tuple它在列表中,并且由于namedtuple是 的子类tuple,因此它也适用于它们。(即因为isinstance(friend_instance, tuple)True)。

这就是为什么Friend永远不会调用用于处理类实例的代码的原因。

下面是一个解决方法-即通过创建一个简单的Wrapper类,其实例不会是一个类型的json.JSONEncoder认为它已经知道如何处理,然后指定default=关键字参数函数,它不每当遇到一个对象,被称为的已经知道该怎么做了。

这就是我的意思:

import json
from collections import namedtuple

class Wrapper(object):
    """ Container class for objects with an _asdict() method. """
    def __init__(self, obj):
        assert hasattr(obj, '_asdict'), 'Cannot wrap object with no _asdict method'
        self.obj = obj


if __name__ == '__main__':

    Friend = namedtuple("Friend", ["id", 'f_name', 'l_name'])
    t = Friend(21, 'Steve', 'Rogerson')
    print(t)
    print(json.dumps(t))
    print(json.dumps(Wrapper(t), default=lambda wrapper: wrapper.obj._asdict()))
Run Code Online (Sandbox Code Playgroud)

输出:

import json
from collections import namedtuple

class Wrapper(object):
    """ Container class for objects with an _asdict() method. """
    def __init__(self, obj):
        assert hasattr(obj, '_asdict'), 'Cannot wrap object with no _asdict method'
        self.obj = obj


if __name__ == '__main__':

    Friend = namedtuple("Friend", ["id", 'f_name', 'l_name'])
    t = Friend(21, 'Steve', 'Rogerson')
    print(t)
    print(json.dumps(t))
    print(json.dumps(Wrapper(t), default=lambda wrapper: wrapper.obj._asdict()))
Run Code Online (Sandbox Code Playgroud)

有关其他信息和见解,还可以查看对相关问题Making object JSON serializable with regular encoder 的回答