为什么JSONEncoder不适用于namedtuples?

时间:2017-07-16 11:49:04

标签: python json data-structures

我无法将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如何JSONize 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应该返回完全 dict(nt._asdict())之类的数据,其中nt是预先定义的namedtuple

1 个答案:

答案 0 :(得分:3)

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

table of types supported by default

请注意,tuple位于列表中,由于namedtupletuple的子类,因此它也适用于它们。 (即isinstance(friend_instance, tuple)True)。

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

以下是绕过该问题的一种方法 - 即创建一个Wrapper类,其赢得的实例是它认为已经知道如何处理的类型,然后使用自定义JSONEncoder来监视它们并执行您想要的操作。这就是我的意思:

import json
from collections import namedtuple

class Wrapper(object):
    def __init__(self, obj):
        self.obj = obj

    def _asdict(self):
        return self.obj._asdict()

Friend = namedtuple("Friend", ["id", 'f_name', 'l_name'])
t = Friend(21, 'Steve', 'Rogerson')
print(t)

class FriendEncoder(json.JSONEncoder):
    """ Watch for objects that are Friend instances and convert them into
        dictionaries that can then be converted into a JSON formatted object
        by the built-in default JSONEncoder.
    """
    def default(self, obj):
        if isinstance(obj, Wrapper):
            return obj._asdict()
        return json.JSONEncoder.default(self, obj)

print(json.dumps(Wrapper(t), cls=FriendEncoder))

输出:

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

有关其他一些信息和见解,请查看问题my answerMaking object JSON serializable with regular encoder