コレクションに重複が含まれないことを確認するために、__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
から返される値はキーごとに送信されますか、それともオブジェクト全体を見るだけの一般的なインクルード/破棄ですか?そのメソッドはネストされた値にどのように対応しますか?私は以前の質問に目を通しましたが、ケース固有のエンコーディングへの最適なアプローチを見つけることができないようです(残念ながら、ここで行う必要があるように思えます)。
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')]
あるいは、 YAML 、 Twisted Jelly 、またはPythonの pickleモジュール などのより汎用的なシリアル化手法を使用すると便利な場合があります。これらはそれぞれ、はるかに広い範囲のデータ型をサポートしています。
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"]'
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')]
JSONでは、辞書、リスト、およびプリミティブオブジェクトタイプ(int、string、bool)のみを使用できます。
一般的な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