102

将 a 序列化为namedtuplejson 并保留字段名称的推荐方法是什么?

将 anamedtuple序列化为 json 只会导致值被序列化,并且字段名称在翻译中丢失。我希望在 json 化时也保留这些字段,因此执行以下操作:

class foobar(namedtuple('f', 'foo, bar')):
    __slots__ = ()
    def __iter__(self):
        yield self._asdict()

上面的内容按照我的预期序列化为 json ,并且namedtuple在我使用的其他地方(属性访问等)中表现得一样,除了在迭代它时会出现类似非元组的结果(这对我的用例来说很好)。

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

4

11 回答 11

93

如果它只是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}'
于 2013-04-03T23:55:52.933 回答
57

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

>>> f = foobar('a', 1)
>>> f[0]
'a'

从本质上讲,namedtuple 非常适合 JSON,因为它实际上是一种自定义构建的类型,其键名作为类型定义的一部分是固定的,这与将键名存储在实例中的字典不同。这可以防止您“往返”命名元组,例如,如果没有其他一些信息,您无法将字典解码回命名元组,例如 dict 中特定于应用程序的类型标记{'a': 1, '#_type': 'foobar'},这有点 hacky。

这并不理想,但如果您只需要将命名元组编码到字典中,另一种方法是扩展或修改您的 JSON 编码器以对这些类型进行特殊处理。这是一个子类化 Python 的示例json.JSONEncoder。这解决了确保嵌套命名元组正确转换为字典的问题:

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"}}
于 2011-05-06T14:52:48.313 回答
22

看起来您曾经能够通过子类simplejson.JSONEncoder化来完成这项工作,但使用最新的 simplejson 代码,情况不再如此:您必须实际修改项目代码。我看不出为什么 simplejson 不应该支持 namedtuples,所以我分叉了这个项目,添加了 namedtuple 支持,我目前正在等待我的分支被拉回主项目。如果您现在需要修复,只需从我的 fork 中拉出即可。

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

于 2011-07-04T13:21:09.253 回答
6

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

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

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

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

于 2018-04-07T22:20:22.137 回答
3

有一个更方便的解决方案是使用装饰器(它使用受保护的字段_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))

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))
于 2018-09-07T14:19:27.403 回答
3

它递归地将 namedTuple 数据转换为 json。

print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='2@mai.com'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='2@mai.com', phone_number=123123), type='image', content='text', media_url='h.com', la=123123, ls=4512313)

def reqursive_to_json(obj):
    _json = {}

    if isinstance(obj, tuple):
        datas = obj._asdict()
        for data in datas:
            if isinstance(datas[data], tuple):
                _json[data] = (reqursive_to_json(datas[data]))
            else:
                 print(datas[data])
                _json[data] = (datas[data])
    return _json

data = reqursive_to_json(m1)
print(data)
{'agent': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}
于 2018-03-22T13:08:52.010 回答
2

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

最好使用更健壮的库,例如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))

=>

{
    "width":10,
    "height":20
}
于 2020-05-22T05:55:26.287 回答
2

jsonplus库为 NamedTuple 实例提供了一个序列化程序如果需要,使用其兼容模式输出简单对象,但更喜欢默认模式,因为它有助于解码。

于 2019-07-01T20:46:13.850 回答
1

这是一个老问题。然而:

对所有有相同问题的人的建议,请仔细考虑使用 的任何私有或内部功能,NamedTuple因为它们以前有并且会随着时间的推移再次改变。

例如,如果您NamedTuple是一个平面值对象,并且您只对序列化它感兴趣,而不是在它嵌套到另一个对象的情况下,您可以避免__dict__被删除或_as_dict()更改带来的麻烦,只需执行类似的操作(是的,这是 Python 3,因为这是目前的答案):

from typing import NamedTuple

class ApiListRequest(NamedTuple):
  group: str="default"
  filter: str="*"

  def to_dict(self):
    return {
      'group': self.group,
      'filter': self.filter,
    }

  def to_json(self):
    return json.dumps(self.to_dict())

我尝试使用default可调用的 kwarg 来dumps进行调用(如果可用),但是由于可转换为列表to_dict(),因此没有调用它。NamedTuple

于 2018-11-29T13:00:19.227 回答
1

simplejson.dump()而不是json.dump做这项工作。不过可能会慢一些。

于 2020-11-05T00:13:53.443 回答
0

这是我对这个问题的看法。它序列化 NamedTuple,处理折叠的 NamedTuple 和其中的 Lists

def recursive_to_dict(obj: Any) -> dict:
_dict = {}

if isinstance(obj, tuple):
    node = obj._asdict()
    for item in node:
        if isinstance(node[item], list): # Process as a list
            _dict[item] = [recursive_to_dict(x) for x in (node[item])]
        elif getattr(node[item], "_asdict", False): # Process as a NamedTuple
            _dict[item] = recursive_to_dict(node[item])
        else: # Process as a regular element
            _dict[item] = (node[item])
return _dict
于 2020-05-14T09:29:16.920 回答