将Python namedtuple序列化为json

cal*_*shy 69 python json namedtuple

namedtuple保留字段名称的情况下序列化到json 的推荐方法是什么?

将a namedtuple序列化为json只会导致序列化的值和字段名称在翻译中丢失.我想在json化时保留字段,因此做了以下事情:

class foobar(namedtuple('f', 'foo, bar')):
    __slots__ = ()
    def __iter__(self):
        yield self._asdict()
Run Code Online (Sandbox Code Playgroud)

上面按照我的预期序列化到json,并且namedtuple在我使用的其他地方行为(属性访问等),除了在迭代它时使用非元组结果(这对我的用例来说很好).

在保留字段名称的情况下转换为json的"正确方法"是什么?

ben*_*lme 59

如果它只是namedtuple你要序列化的一个,使用它的_asdict()方法将起作用(Python> = 2.7)

>>> from collections import namedtuple
>>> import json
>>> FB = namedtuple("FB", ("foo", "bar"))
>>> fb = FB(123, 456)
>>> json.dumps(fb._asdict())
'{"foo": 123, "bar": 456}'
Run Code Online (Sandbox Code Playgroud)

  • `fb._asdict()`或`vars(fb)`会更好. (5认同)
  • 我得到了AttributeError:在Windows上运行Python 2.7(x64)中的代码时,'FB'对象没有属性'__dict__'.但是fb._asdict()工作正常. (4认同)
  • 在python 3中,`__ dict__`已被删除.`_asdict`似乎适用于两者. (3认同)

sam*_*ias 49

这非常棘手,因为它namedtuple()是一个返回派生自的新类型的工厂tuple.一种方法是让你的类继承UserDict.DictMixin,但是tuple.__getitem__已经定义并期望一个表示元素位置的整数,而不是它的属性名称:

>>> f = foobar('a', 1)
>>> f[0]
'a'
Run Code Online (Sandbox Code Playgroud)

在其核心,namedtuple是一个奇怪的JSON,因为它实际上是一个自定义类型,其键名作为类型定义的一部分被修复,不像一个字典,其中键名存储在实例中.这可以防止你"绕过"一个namedtuple,例如你不能将字典解码回一个namedTuple而没有其他一些信息,比如dict中特定于应用程序的类型标记{'a': 1, '#_type': 'foobar'},这有点像hacky.

这并不理想,但如果您只需要将 namedtuples 编码为字典,另一种方法是将JSON编码器扩展或修改为特殊情况下的这些类型.这是一个子类化Python的例子json.JSONEncoder.这解决了确保嵌套的namedtuples正确转换为字典的问题:

from collections import namedtuple
from json import JSONEncoder

class MyEncoder(JSONEncoder):

    def _iterencode(self, obj, markers=None):
        if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
            gen = self._iterencode_dict(obj._asdict(), markers)
        else:
            gen = JSONEncoder._iterencode(self, obj, markers)
        for chunk in gen:
            yield chunk

class foobar(namedtuple('f', 'foo, bar')):
    pass

enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
    print enc.encode(obj)

{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}
Run Code Online (Sandbox Code Playgroud)

  • 啊,在python 2.7+ _iterencode中不再是JSONEncoder的方法. (17认同)
  • _它的核心是一个奇怪的JSON,因为它实际上是一个自定义类型,其键名作为类型定义的一部分被修复,不像一个字典,其中键名存储在实例中._非常有见地的注释.我没有想过这个.谢谢.我喜欢namedtuples,因为它们提供了一个很好的不可变结构*,具有*属性命名方便性.我会接受你的回答.话虽如此,Java的序列化机制提供了对_how_对象序列化的更多控制,我很想知道为什么这样的钩子似乎不存在于Python中. (11认同)
  • `>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)` `<<< '["x", "y"]'` (2认同)
  • @calvin谢谢,我发现namedtuple也很有用,希望有一个更好的解决方案来递归编码到JSON.@zeekay是的,似乎在2.7+他们隐藏它所以它不能再被覆盖.这令人失望. (2认同)

sin*_*boy 20

看起来你曾经能够通过子类simplejson.JSONEncoder来实现这个功能,但是使用最新的simplejson代码,情况就不再那样了:你必须实际修改项目代码.我看不出为什么simplejson不应该支持namedtuples,所以我分叉了项目,添加了namedtuple支持,我正在等待我的分支被拉回到主项目中.如果你现在需要修复,只需从我的叉子拉.

编辑:看起来simplejson现在的最新版本本身支持这个namedtuple_as_object选项,默认为True.

  • 您的编辑是正确的答案.simplejson序列化了不同于json的namedtuples(我认为:更好).这真的使模式:"尝试:导入simplejson作为json,除了:import json",风险很大,因为你可能会在某些机器上获得不同的行为,具体取决于是否安装了simplejson.出于这个原因,我现在需要在我的许多设置文件中使用simplejson并且不使用该模式. (3认同)
  • @marr75 - `ujson` 也是如此,在这种极端情况下,这更加奇怪和不可预测...... (2认同)

LtW*_*orf 6

我为此编写了一个库:https : //github.com/ltworf/typedload

它可以往返于命名元组并返回。

它支持非常复杂的嵌套结构,包括列表、集合、枚举、联合、默认值。它应该涵盖最常见的情况。

编辑:该库还支持 dataclass 和 attr 类。


小智 5

有一个更方便的解决方案是使用装饰器(它使用 protected field _fields)。

Python 2.7+:

import json
from collections import namedtuple, OrderedDict

def json_serializable(cls):
    def as_dict(self):
        yield OrderedDict(
            (name, value) for name, value in zip(
                self._fields,
                iter(super(cls, self).__iter__())))
    cls.__iter__ = as_dict
    return cls

#Usage:

C = json_serializable(namedtuple('C', 'a b c'))
print json.dumps(C('abc', True, 3.14))

# or

@json_serializable
class D(namedtuple('D', 'a b c')):
    pass

print json.dumps(D('abc', True, 3.14))
Run Code Online (Sandbox Code Playgroud)

Python 3.6.6+:

import json
from typing import TupleName

def json_serializable(cls):
    def as_dict(self):
        yield {name: value for name, value in zip(
            self._fields,
            iter(super(cls, self).__iter__()))}
    cls.__iter__ = as_dict
    return cls

# Usage:

@json_serializable
class C(NamedTuple):
    a: str
    b: bool
    c: float

print(json.dumps(C('abc', True, 3.14))
Run Code Online (Sandbox Code Playgroud)

  • @LtWorf你的库也使用`_fields`;-) https://github.com/ltworf/typedload/blob/master/typedload/datadumper.py 它是namedtuple的公共API的一部分,实际上:https://docs.python。 org/3.7/library/collections.html#collections.namedtuple 人们会对下划线感到困惑(难怪!)。这是糟糕的设计,但我不知道他们还有什么其他选择。 (2认同)

mik*_*dge 5

使用本机 python json 库无法正确序列化命名元组。它将始终将元组视为列表,并且不可能覆盖默认序列化器来更改此行为。如果对象是嵌套的,情况会更糟。

最好使用更强大的库,例如orjson

import orjson
from typing import NamedTuple

class Rectangle(NamedTuple):
    width: int
    height: int

def default(obj):
    if hasattr(obj, '_asdict'):
        return obj._asdict()

rectangle = Rectangle(width=10, height=20)
print(orjson.dumps(rectangle, default=default))
Run Code Online (Sandbox Code Playgroud)

=>

{
    "width":10,
    "height":20
}
Run Code Online (Sandbox Code Playgroud)