使用 Python >= 2.7 将嵌套的命名元组序列化为 JSON

Posted

技术标签:

【中文标题】使用 Python >= 2.7 将嵌套的命名元组序列化为 JSON【英文标题】:Serializing a nested namedtuple into JSON with Python >= 2.7 【发布时间】:2013-06-01 01:35:33 【问题描述】:

我有一个类似CalvinKrishy's problem 的问题 Samplebias 的解决方案不适用于我拥有的数据。

我正在使用 Python 2.7。

这是数据:

命名元组

>>> a_t = namedtuple('a','f1 words')
>>> word_t = namedtuple('word','f2 value')
>>> w1 = word_t(f2=[0,1,2], value='abc')
>>> w2 = word_t(f2=[3,4], value='def')
>>> a1 = a_t(f1=[0,1,2,3,4],words=[w1, w2])
>>> a1
a(f1=[0, 1, 2, 3, 4], words=[word(f2=[0, 1, 2], value='abc'), word(f2=[3, 4], value='def')])

字典

>>> w3 = 
>>> w3['f2'] = [0,1,2]
>>> w3['value'] = 'abc'
>>> w4 = 
>>> w4['f2'] = [3,4]
>>> w4['value'] = 'def'
>>> a2 = 
>>> a2['f1'] = [0, 1, 2, 3, 4]
>>> a2['words'] = [w3,w4]
>>> a2
'f1': [0, 1, 2, 3, 4], 'words': ['f2': [0, 1, 2], 'value': 'abc', 'f2': [3, 4], 'value': 'def']

你可以看到 a1 和 a2 都是一样的,除了一个是 namedtuple 而另一个是 dict

但是 json.dumps 不同:

>>> json.dumps(a1._asdict())
'"f1": [0, 1, 2, 3, 4], "words": [[[0, 1, 2], "abc"], [[3, 4], "def"]]'
>>> json.dumps(a2)
'"f1": [0, 1, 2, 3, 4], "words": ["f2": [0, 1, 2], "value": "abc", "f2": [3, 4], "value": "def"]'

我希望 a1 的 json 格式与它为 a2 所做的完全一样。

【问题讨论】:

namedtuple 是 tuple 的子类,因此 JSON 按其应有的方式将它们序列化为列表。 但是 docs.python.org/dev/library/… 返回一个 OrderedDict 那是一个实用方法;将您的元组转换为有序字典,然后再序列化为 JSON。 您可以创建编码器的自定义子类来处理命名元组:Overriding nested JSON encoding of inherited default supported objects like dict, list @MartijnPieters 在这种情况下,您不能覆盖 json 编码器的行为,因为它将命名元组视为元组。 【参考方案1】:

这是我使用的版本,改编自宫城先生的。我将isinstancecollections.abc 一起使用,而不是hasattr,并在生成的dict 中包含了一个_type 键,并带有namedtuple 类的名称。

import collections.abc

def _nt_to_dict(obj):
    recurse = lambda x: map(_nt_to_dict, x)
    obj_is = lambda x: isinstance(obj, x)
    if obj_is(tuple) and hasattr(obj, '_fields'):  # namedtuple
        fields = zip(obj._fields, recurse(obj))
        class_name = obj.__class__.__name__
        return dict(fields, **'_type': class_name)
    elif obj_is(collections.abc.Mapping):
        return type(obj)(zip(obj.keys(), recurse(obj.values())))
    elif obj_is(collections.abc.Iterable) and not obj_is(str):
        return type(obj)(recurse(obj))
    else:
        return obj

【讨论】:

【参考方案2】:

问题在于使用namedtuple._asdict,而不是json.dumps。如果您查看带有namedtuple(..., verbose=True) 的代码,您会看到:

def _asdict(self):
    'Return a new OrderedDict which maps field names to their values'
    return OrderedDict(zip(self._fields, self))

只有顶层实际上变成了OrderedDict,所有包含的元素都保持不变。这意味着嵌套的 namedtuples 仍然是 tuple 子类,并且会(正确)序列化等等。

如果您可以接受对特定转换函数的调用(例如对_asdict 的调用),您可以编写自己的函数。

def namedtuple_asdict(obj):
  if hasattr(obj, "_asdict"): # detect namedtuple
    return OrderedDict(zip(obj._fields, (namedtuple_asdict(item) for item in obj)))
  elif isinstance(obj, basestring): # iterables - strings
     return obj
  elif hasattr(obj, "keys"): # iterables - mapping
     return OrderedDict(zip(obj.keys(), (namedtuple_asdict(item) for item in obj.values())))
  elif hasattr(obj, "__iter__"): # iterables - sequence
     return type(obj)((namedtuple_asdict(item) for item in obj))
  else: # non-iterable cannot contain namedtuples
    return obj

json.dumps(namedtuple_asdict(a1))
# prints '"f1": [0, 1, 2, 3, 4], "words": ["f2": [0, 1, 2], "value": "abc", "f2": [3, 4], "value": "def"]'

如您所见,最大的问题是嵌套结构不是 namedtuples 但可以包含它们。

【讨论】:

只是一点警告:这个函数对于任何循环数据结构都是不安全的。 对于 Python 3,将 sn-p 中的 basestring 替换为 str

以上是关于使用 Python >= 2.7 将嵌套的命名元组序列化为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Python 2.7环境搭建

为啥我必须将 Qt Designer 2.7 与 Python 2.7 一起使用?

Python 2.7:setlogmask(0) 不禁用系统日志

mac下的Python解释器(2.7->3.4)升级后,第三方库无法使用

Python 2.7复制并粘贴超链接文本

(Python 2.7)获取UI标题栏大小