web-dev-qa-db-ja.com

セットをJSONシリアル化する方法は?

コレクションに重複が含まれないことを確認するために、__hash__および__eq__メソッドを持つオブジェクトを含むPython setがあります。

この結果setをjsonエンコードする必要がありますが、空のsetでさえjson.dumpsメソッドに渡すとTypeErrorが発生します。

  File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python2.7/json/encoder.py", line 178, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: set([]) is not JSON serializable

カスタムdefaultメソッドを持つjson.JSONEncoderクラスの拡張を作成できることは知っていますが、setの変換をどこから始めればよいのかわかりません。デフォルトメソッド内のset値からディクショナリを作成し、そのエンコーディングを返しますか?理想的には、元のエンコーダーがチョークするすべてのデータ型をデフォルトのメソッドで処理できるようにしたいと思います(データソースとしてMongoを使用しているため、日付でもこのエラーが発生するようです)

正しい方向へのヒントをいただければ幸いです。

編集:

答えてくれてありがとう!おそらく私はもっと正確にすべきだった。

ここでの回答を利用して(そしてそれを支持して)翻訳されるsetの制限を回避しましたが、問題のある内部キーもあります。

setのオブジェクトは、__dict__に変換される複雑なオブジェクトですが、jsonエンコーダーの基本型には適さない可能性があるプロパティの値を含むこともできます。

このsetにはさまざまな種類があり、ハッシュは基本的にエンティティの一意のIDを計算しますが、NoSQLの真の精神では、子オブジェクトに何が含まれているか正確にはわかりません。

1つのオブジェクトにはstartsの日付値が含まれる場合がありますが、別のオブジェクトには「非プリミティブ」オブジェクトを含むキーを含まない他のスキーマが含まれる場合があります。

だからこそ、私が考えることができる唯一の解決策は、JSONEncoderを拡張してdefaultメソッドを置き換え、異なるケースをオンにすることでした-しかし、これをどのようにすればいいのかわかりません。ネストされたオブジェクトでは、defaultから返される値はキーごとに送信されますか、それともオブジェクト全体を見るだけの一般的なインクルード/破棄ですか?そのメソッドはネストされた値にどのように対応しますか?私は以前の質問に目を通しましたが、ケース固有のエンコーディングへの最適なアプローチを見つけることができないようです(残念ながら、ここで行う必要があるように思えます)。

125
DeaconDesperado

JSON 表記法には少数のネイティブデータ型(オブジェクト、配列、文​​字列、数値、ブール値、null)しか含まれていないため、JSONでシリアル化されたものはこれらのいずれかの型として表現する必要があります。

json module docs に示されているように、この変換はJSONEncoderおよびJSONDecoder、しかし、あなたはあなたが必要とするかもしれない他の構造をあきらめているでしょう(セットをリストに変換する場合、通常のリストを回復する能力を失います;セットを変換する場合dict.fromkeys(s)を使用するディクショナリは、ディクショナリを回復する機能を失います)。

より洗練されたソリューションは、他のネイティブJSONタイプと共存できるカスタムタイプを構築することです。これにより、リスト、セット、辞書、小数、日時オブジェクトなどを含むネスト構造を保存できます。

from json import dumps, loads, JSONEncoder, JSONDecoder
import pickle

class PythonObjectEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (list, dict, str, unicode, int, float, bool, type(None))):
            return JSONEncoder.default(self, obj)
        return {'_python_object': pickle.dumps(obj)}

def as_python_object(dct):
    if '_python_object' in dct:
        return pickle.loads(str(dct['_python_object']))
    return dct

リスト、辞書、およびセットを処理できることを示すサンプルセッションを次に示します。

>>> data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]

>>> j = dumps(data, cls=PythonObjectEncoder)

>>> loads(j, object_hook=as_python_object)
[1, 2, 3, set(['knights', 'say', 'who', 'ni']), {u'key': u'value'}, Decimal('3.14')]

あるいは、 YAMLTwisted Jelly 、またはPythonの pickleモジュール などのより汎用的なシリアル化手法を使用すると便利な場合があります。これらはそれぞれ、はるかに広い範囲のデータ型をサポートしています。

99

listに遭遇したときにsetを返すカスタムエンコーダーを作成できます。以下に例を示します。

>>> import json
>>> class SetEncoder(json.JSONEncoder):
...    def default(self, obj):
...       if isinstance(obj, set):
...          return list(obj)
...       return json.JSONEncoder.default(self, obj)
... 
>>> json.dumps(set([1,2,3,4,5]), cls=SetEncoder)
'[1, 2, 3, 4, 5]'

この方法でも他のタイプを検出できます。リストが実際にセットであることを保持する必要がある場合は、カスタムエンコーディングを使用できます。 return {'type':'set', 'list':list(obj)}のようなものが動作する可能性があります。

ネストされた型を図解するには、これを直列化することを検討してください。

>>> class Something(object):
...    pass
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)

これにより、次のエラーが発生します。

TypeError: <__main__.Something object at 0x1691c50> is not JSON serializable

これは、エンコーダが返されたlistの結果を取得し、その子に対してシリアライザを再帰的に呼び出すことを示します。複数のタイプのカスタムシリアライザーを追加するには、次のようにします。

>>> class SetEncoder(json.JSONEncoder):
...    def default(self, obj):
...       if isinstance(obj, set):
...          return list(obj)
...       if isinstance(obj, Something):
...          return 'CustomSomethingRepresentation'
...       return json.JSONEncoder.default(self, obj)
... 
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
'[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'
93
jterrace

Raymond Hettingerのソリューション をpythonに適合させました3。

変更点は次のとおりです。

  • unicodeが消えました
  • 親のdefaultへの呼び出しをsuper()で更新しました
  • base64を使用してbytes型をstrにシリアル化します(python 3のbytesはJSONに変換できないようです)
from decimal import Decimal
from base64 import b64encode, b64decode
from json import dumps, loads, JSONEncoder
import pickle

class PythonObjectEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (list, dict, str, int, float, bool, type(None))):
            return super().default(obj)
        return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}

def as_python_object(dct):
    if '_python_object' in dct:
        return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
    return dct

data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
j = dumps(data, cls=PythonObjectEncoder)
print(loads(j, object_hook=as_python_object))
# prints: [1, 2, 3, {'knights', 'who', 'say', 'ni'}, {'key': 'value'}, Decimal('3.14')]
5
simlmx

JSONでは、辞書、リスト、およびプリミティブオブジェクトタイプ(int、string、bool)のみを使用できます。

5
Joseph Le Brech

一般的なPythonオブジェクトではなくセットのみをエンコードする必要があり、人間が読みやすいものにしたい場合は、レイモンド・ヘッティンガーの答えの簡略版を使用できます。

import json
import collections

class JSONSetEncoder(json.JSONEncoder):
    """Use with json.dumps to allow Python sets to be encoded to JSON

    Example
    -------

    import json

    data = dict(aset=set([1,2,3]))

    encoded = json.dumps(data, cls=JSONSetEncoder)
    decoded = json.loads(encoded, object_hook=json_as_python_set)
    assert data == decoded     # Should assert successfully

    Any object that is matched by isinstance(obj, collections.Set) will
    be encoded, but the decoded value will always be a normal Python set.

    """

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        else:
            return json.JSONEncoder.default(self, obj)

def json_as_python_set(dct):
    """Decode json {'_set_object': [1,2,3]} to set([1,2,3])

    Example
    -------
    decoded = json.loads(encoded, object_hook=json_as_python_set)

    Also see :class:`JSONSetEncoder`

    """
    if '_set_object' in dct:
        return set(dct['_set_object'])
    return dct
3
NeilenMarais