【问题标题】:Why doesn't JSONEncoder work for namedtuples?为什么 JSONEncoder 不适用于命名元组?
【发布时间】:2017-07-16 11:49:04
【问题描述】:

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

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

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

第二个,现在考虑以下示例,它告诉 Python 如何 JSON 化 Friend 对象:

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

最后当我们尝试使用 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)

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

为什么?

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


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

【问题讨论】:

  • default 方法不会被 tuplelist 等的子类调用。
  • @vaultah 我同意 default 方法没有被调用。但是你能证实“tuplelist等的子类”
  • json.JSONEncoder 仅在它不知道如何序列化自身的对象上调用 default (),其中包括 tuples、lists 和 dicts(以及它们的子类) )。这意味着为了做你想做的事,你需要“欺骗”它。
  • 请描述你认为/想要一个已经“真正”转换为 JSON 的 namedtuple 会是什么样子,因为这样的东西不是定义的 JSON object 另外,你如何期望它如果它是非标准的,以后再解码?
  • "json.JSONEncoder 只调用 default () 对它还不知道如何序列化自身的对象,其中包括 tupleslists 和 ..." 我无法理解底层源码(json/encoder.py)能解释一下吗?

标签: python json data-structures


【解决方案1】:

正如我在评论中所说,json.JSONEncoder 仅在遇到不知道如何序列化自身的对象类型时才调用default。在json 文档中有一个table of them。这是它的屏幕截图,方便参考:

注意tuple 在列表中,因为namedtupletuple 的子类,所以它也适用于它们。 (即因为isinstance(friend_instance, tuple)True)。

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

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

这就是我的意思:

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 wrapped: wrapped.obj._asdict()))

输出:

Friend(id=21, f_name='Steve', l_name='Rogerson')
[21, "Steve", "Rogerson"]
{"id": 21, "f_name": "Steve", "l_name": "Rogerson"}

如需更多信息和见解,请查看my answer 相关问题Making object JSON serializable with regular encoder

【讨论】:

  • 我正在寻找类似问题的解决方案。非常感谢。
猜你喜欢
  • 1970-01-01
  • 2018-11-17
  • 2010-10-16
  • 2015-11-12
  • 2021-08-26
  • 1970-01-01
  • 2011-03-21
  • 2011-05-22
  • 2012-08-11
相关资源
最近更新 更多