web-dev-qa-db-ja.com

Python namedtupleをjsonにシリアル化する

namedtuple をフィールド名を保持したままjsonにシリアル化する推奨方法は何ですか?

namedtupleをjsonにシリアル化すると、値はシリアル化され、フィールド名は翻訳で失われます。 json化したときにフィールドも保持したいので、次のようにしました。

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

上記は私が期待するようにjsonにシリアライズし、私が使用している他の場所(属性アクセスなど)でnamedtupleとして動作しますが、反復中にTupleのような結果ではありません(私のユースケースではうまくいきます)。

フィールド名を保持したままjsonに変換する「正しい方法」とは何ですか?

65
calvinkrishy

namedtuple()Tupleから派生した新しい型を返すファクトリであるため、これはかなり注意が必要です。 1つのアプローチは、クラスにUserDict.DictMixinからも継承させることですが、Tuple.__getitem__は既に定義されており、属性の名前ではなく要素の位置を示す整数を想定しています。

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

本質的には、namedtupleは、キー定義が型定義の一部として固定されているカスタムビルド型であるため、JSONに奇妙に適合します 、キー名がインスタンス内に保存される辞書とは異なります。これにより、名前付きタプルを「ラウンドトリップ」できなくなります。 dict {'a': 1, '#_type': 'foobar'}にあるアプリ固有のタイプマーカーのように、少しハッキングされている他の情報がなければ辞書をデコードしてnamedtupleに戻すことはできません。

これは理想的ではありませんが、namedtuplesを辞書にエンコードするだけでよい場合、別のアプローチは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"}}
47
samplebias

シリアル化する必要があるnamedtupleが1つだけの場合、_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}'
53
benselme

これを使用して_simplejson.JSONEncoder_をサブクラス化することができたように見えますが、最新のsimplejsonコードではそうではなく、実際にプロジェクトコードを変更する必要があります。 simplejsonがnamedtuplesをサポートしない理由はないので、プロジェクトを分岐し、namedtupleサポートを追加しました。そして、私は 現在、ブランチがメインプロジェクトに戻されるのを待っています です。今すぐ修正が必要な場合は、フォークから引き出してください。

[〜#〜] edit [〜#〜]simplejsonの最新バージョンが_namedtuple_as_object_オプション。デフォルトはTrueです。

20
singingwolfboy

これを行うためのライブラリを作成しました: https://github.com/ltworf/typedload

名前付きタプルとの間を行き来できます。

リスト、セット、列挙、ユニオン、デフォルト値を含む非常に複雑なネスト構造をサポートします。最も一般的なケースをカバーする必要があります。

編集:ライブラリは、dataclassおよびattrクラスもサポートします。

4
LtWorf

NamedTupleデータをjsonに再帰的に変換します。

print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='[email protected]'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='[email protected]', 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': '[email protected]',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '[email protected]',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}
1
Tolgahan ÜZÜN

より便利な解決策は、デコレータを使用することです(保護フィールド_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))
1
Dmitry T.

jsonplus ライブラリは、NamedTupleインスタンスのシリアライザーを提供します。互換モードを使用して、必要に応じて単純なオブジェクトを出力しますが、デコードバックに役立つため、デフォルトを優先します。

0
Gonzalo

これは古い質問です。しかしながら:

同じ質問を持つすべての人への提案は、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が変換可能であるため呼び出されませんでしたリストに。

0
dlamblin