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


87

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

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

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

上記は、私が期待するようにjsonにシリアル化しnamedtuple、反復中の非タプルのような結果(私のユースケースには問題ありません)を除いて、私が使用する他の場所(属性アクセスなど)と同じように動作します。

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


回答:


55

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

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

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

これは理想的ではありませんが名前付きタプルを辞書にエンコードするだけでよい場合は、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"}}

12
名前付きタプルは、キー名がインスタンス内に格納されている辞書とは異なり、実際にはキー名が型定義の一部として固定されているカスタムビルドの型であるため、本質的にJSONには奇妙に適合します。非常に洞察に満ちたコメント。私はそれについて考えていませんでした。ありがとう。名前付きタプルは、属性の名前付けに便利な不変の構造提供するので、私は好きです。私はあなたの答えを受け入れます。そうは言っても、Javaのシリアル化メカニズムは、オブジェクトのシリアル化方法をより細かく制御できるので、そのようなフックがPythonに存在しないように見える理由を知りたいと思います。
calvinkrishy

それが私の最初のアプローチでしたが、実際には機能しません(とにかく私にとっては)。
zeekay

1
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder) <<< '["x", "y"]'
zeekay

19
ああ、Python 2.7以降では、_iterencodeはJSONEncoderのメソッドではなくなりました。
zeekay

2
@calvinありがとう、namedtupleも便利だと思います。再帰的にJSONにエンコードするためのより良いソリューションがあればいいのにと思います。@zeekayはい、2.7以降では非表示になっているため、上書きできなくなりました。それは残念です。
samplebias

77

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}'

4
WindowsのPython2.7(x64)でそのコードを実行すると、AttributeErrorが発生します: 'FB'オブジェクトに属性 ' dict 'がありません。ただし、fb._asdict()は正常に機能します。
geographika

5
fb._asdict()またはvars(fb)より良いでしょう。
jpmc26 2015年

1
@ jpmc26 :。のvarsないオブジェクトでは使用できません__dict__
Rufflewind 2016年

@Rufflewind__dict__それらにも使用できません。=)
jpmc26 2016年

4
Pythonでは3__dict__が削除されました。_asdict両方で動作するようです。
Andy Hayden

21

以前はサブクラス化simplejson.JSONEncoderしてこれを機能させることができたようですが、最新のsimplejsonコードでは、現在はそうではありません。実際にプロジェクトコードを変更する必要があります。simplejsonがnamedtuplesをサポートすべきでない理由がわからないので、プロジェクトをフォークし、namedtupleサポートを追加しました。現在、ブランチがメインプロジェクトに戻されるのを待っています。今すぐ修正が必要な場合は、フォークから引っ張ってください。

編集simplejson現在の最新バージョンはnamedtuple_as_object、デフォルトでに設定されているオプションでこれをネイティブにサポートしているようTrueです。


3
あなたの編集は正解です。simplejsonは、名前付きタプルをjsonとは異なる方法でシリアル化します(私の意見:より良い)。これにより、「try:import simplejson as jsonexcept:import json」というパターンが実際に作成されます。これは、simplejsonがインストールされているかどうかによって、一部のマシンで異なる動作が発生する可能性があるため、危険です。そのため、多くのセットアップファイルでsimplejsonが必要になり、そのパターンを控えています。
marr75 2012

1
@ marr75 -のための同上ujson、さらに奇妙な予測不可能なエッジケースであり、...
マック

私は(かなり印刷)JSONを使用してシリアライズ再帰namedtupleを得ることができた:simplejson.dumps(my_tuple, indent=4)
KFL

5

私はこれを行うためのライブラリを書きました:https//github.com/ltworf/typedload

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

リスト、セット、列挙型、共用体、デフォルト値など、非常に複雑なネストされた構造をサポートします。最も一般的なケースをカバーする必要があります。

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


2

これは、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'}

1
+1ほぼ同じにしました。しかし、あなたのリターンはjsonではなくdictです。あなたは「"ではない持っている必要があります、そして、あなたのオブジェクトの値がブール値である場合、それは本当に変換されることはありません私は、JSONに変換するjson.dumpsを使用し、それが辞書に変換する方が安全だと思います。。
フレッド・ローラン

2

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

そうしないでください、彼らは常に内部APIを変更します。私のtypedloadライブラリには、さまざまなpyバージョンのケースがいくつかあります。
ltWorf 2018

はい、それは明らかです。ただし、テストせずに新しいPythonバージョンに移行することはできません。また、他のソリューション_asdictでは、「保護された」クラスメンバーでもあるを使用します。
ドミトリー

1
LtWorf、ライブラリはGPLであり、frozensetsでは機能しません
Thomas Grainger

2
@LtWorfライブラリも_fields;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.pyを使用します。 これはnamedtupleのパブリックAPIの一部であり、実際には次のようになります。docs.python.org / 3.7 / library / 人々は混乱しますアンダースコア(不思議ではありません!)。悪いデザインですが、他にどんな選択肢があったのかわかりません。
quant_dev 2018

1
どんな物?いつ?リリースノートを引用できますか?
quant_dev 2018

2

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


ここで他のソリューションを調べたところ、この依存関係を追加するだけで多くの時間を節約できたことがわかりました。特に、セッションでjsonとして渡す必要のあるNamedTuplesのリストがあったためです。jsonplusを使用すると、基本的に、名前付きタプルのリストをjsonに出し入れすることができ.dumps()、構成は機能し.loads()ません。
ロブ

1

名前付きタプルをネイティブのpythonjsonライブラリで正しくシリアル化することは不可能です。タプルは常にリストとして表示され、デフォルトのシリアライザーをオーバーライドしてこの動作を変更することはできません。オブジェクトがネストされているとさらに悪化します。

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
}

1
私もファンですorjson
CircleOnCircles

0

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

同じ質問を持つすべての人への提案は、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可能な場合は呼び出しdumpsを行うために呼び出し可能なkwargを使用しようto_dict()としましたNamedTupleが、リストに変換できるため、呼び出されませんでした。


3
_asdict名前付きtupleパブリックAPIの一部です。アンダースコアの理由を説明していますdocs.python.org/3.7/library/… "タプルから継承されたメソッドに加えて、名前付きタプルは3つの追加メソッドと2つの属性をサポートします。フィールド名、メソッド、および属性名との競合を防ぐためにアンダースコアから始めてください。」
quant_dev 2018

@quant_devありがとう、私はその説明を見ませんでした。これはAPIの安定性を保証するものではありませんが、これらのメソッドの信頼性を高めるのに役立ちます。明示的なto_dictの可読性は気に入っていますが、_as_dictを再実装しているように見えます
dlamblin 2018

0

これが私の問題に対する見方です。NamedTupleをシリアル化し、折りたたまれたNamedTuplesとその中のリストを処理します

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.