[英]How to serialize list of list of sets to json for insert into DB column
[英]How to JSON serialize sets?
我有一個 Python set
,其中包含具有__hash__
和__eq__
方法的對象,以確保集合中不包含重復項。
我需要對這個結果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 的真正精神中,並不能准確地說明子對象包含什么。
一個對象可能包含starts
的日期值,而另一個對象可能有一些其他模式,其中不包含包含“非原始”對象的鍵。
這就是為什么我能想到的唯一解決方案是擴展JSONEncoder
以替換default
方法以打開不同的情況 - 但我不確定如何解決這個問題,並且文檔不明確。 在嵌套對象中, default
返回的值是按鍵,還是只是查看整個對象的通用包含/丟棄? 該方法如何適應嵌套值? 我已經查看了以前的問題,似乎找不到針對特定情況進行編碼的最佳方法(不幸的是,這似乎是我需要在這里做的)。
您可以創建一個自定義編碼器,在遇到set
時返回一個list
。 這是一個例子:
>>> 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"]'
JSON表示法只有少數原生數據類型(對象、數組、字符串、數字、布爾值和 null),因此在 JSON 中序列化的任何內容都需要表示為這些類型之一。
如json 模塊 docs所示,這種轉換可以由JSONEncoder和JSONDecoder自動完成,但是您將放棄一些您可能需要的其他結構(如果您將集合轉換為列表,那么您將失去恢復常規的能力列表;如果您使用dict.fromkeys(s)
將集合轉換為字典,那么您將失去恢復字典的能力)。
一個更復雜的解決方案是構建一個可以與其他原生 JSON 類型共存的自定義類型。 這使您可以存儲嵌套結構,包括列表、集合、字典、小數、日期時間對象等:
from json import dumps, loads, JSONEncoder, JSONDecoder
import pickle
class PythonObjectEncoder(JSONEncoder):
def default(self, obj):
try:
return {'_python_object': pickle.dumps(obj).decode('latin-1')}
except pickle.PickleError:
return super().default(obj)
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(dct['_python_object'].encode('latin-1'))
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']), {'key': 'value'}, Decimal('3.14')]
或者,使用更通用的序列化技術(例如YAML 、 Twisted Jelly或 Python 的pickle 模塊)可能會很有用。 這些都支持更大范圍的數據類型。
您不需要制作自定義編碼器類來提供default
方法 - 它可以作為關鍵字參數傳入:
import json
def serialize_sets(obj):
if isinstance(obj, set):
return list(obj)
return obj
json_str = json.dumps(set([1,2,3]), default=serialize_sets)
print(json_str)
在所有受支持的 Python 版本中產生[1, 2, 3]
。
我將Raymond Hettinger 的解決方案改編為 python 3。
以下是發生的變化:
unicode
消失了super()
更新了對父母default
的調用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_string = json.dumps(data, iterable_as_array=True)
這會將所有集合(和其他可迭代對象)轉換為數組。 請注意,當您解析 JSON 時,這些字段將保留為數組。 如果要保留類型,則需要編寫自定義編碼器。
還要確保安裝並需要simplejson
。
你可以在PyPi上找到它。
如果您確定唯一的不可序列化數據將是set
s,那么有一個非常簡單(而且很臟)的解決方案:
json.dumps({"Hello World": {1, 2}}, default=tuple)
只有不可序列化的數據會被default
的函數處理,所以只有set
會被轉換為tuple
。
只有字典、列表和原始對象類型(int、string、bool)在 JSON 中可用。
如果您只需要編碼集合,而不是一般的 Python 對象,並且希望使其易於人類閱讀,可以使用 Raymond Hettinger 答案的簡化版本:
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
@AnttiHaapala 的縮短版:
json.dumps(dict_with_sets, default=lambda x: list(x) if isinstance(x, set) else x)
>>> import json
>>> set_object = set([1,2,3,4])
>>> json.dumps(list(set_object))
'[1, 2, 3, 4]'
公認解決方案的一個缺點是它的輸出非常特定於 python。 即它的原始 json 輸出不能被人類觀察或被另一種語言(例如 javascript)加載。 例子:
db = {
"a": [ 44, set((4,5,6)) ],
"b": [ 55, set((4,3,2)) ]
}
j = dumps(db, cls=PythonObjectEncoder)
print(j)
會給你:
{"a": [44, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsESwVLBmWFcQJScQMu"}], "b": [55, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsCSwNLBGWFcQJScQMu"}]}
我可以提出一個解決方案,將集合降級為包含列表的字典,並在使用相同的編碼器加載到 python 時返回到集合,因此保留可觀察性和語言不可知論:
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)
elif isinstance(obj, set):
return {"__set__": list(obj)}
return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}
def as_python_object(dct):
if '__set__' in dct:
return set(dct['__set__'])
elif '_python_object' in dct:
return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
return dct
db = {
"a": [ 44, set((4,5,6)) ],
"b": [ 55, set((4,3,2)) ]
}
j = dumps(db, cls=PythonObjectEncoder)
print(j)
ob = loads(j)
print(ob["a"])
這讓你:
{"a": [44, {"__set__": [4, 5, 6]}], "b": [55, {"__set__": [2, 3, 4]}]}
[44, {'__set__': [4, 5, 6]}]
請注意,序列化具有鍵"__set__"
的元素的字典將破壞此機制。 所以__set__
現在已成為保留的dict
鍵。 顯然可以隨意使用另一個更深層次的混淆密鑰。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.