我有一个包含__hash__和__eq__方法的对象的Python集合,以确保集合中不包含重复的对象。
我需要json编码这个结果集,但传递甚至一个空集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
我知道我可以为json创建一个扩展。JSONEncoder类,它有一个自定义的默认方法,但我甚至不确定从哪里开始转换集合。我应该在默认方法内创建一个字典的设置值,然后返回编码?理想情况下,我希望使默认方法能够处理原始编码器阻塞的所有数据类型(我使用Mongo作为数据源,因此日期似乎也会引发这个错误)
任何正确方向的提示都将不胜感激。
编辑:
谢谢你的回答!也许我应该说得更准确些。
我利用这里的答案来解决被翻译的集合的限制,但内部键也是一个问题。
集合中的对象是可转换为__dict__的复杂对象,但它们本身也可以包含其属性的值,这些值可能不适用于json编码器中的基本类型。
这个集合中有很多不同的类型,散列基本上为实体计算一个唯一的id,但在NoSQL的真正精神中,并没有确切地告诉子对象包含什么。
一个对象可能包含开始日期值,而另一个对象可能具有一些其他模式,其中不包含包含“非原始”对象的键。
That is why the only solution I could think of was to extend the JSONEncoder to replace the default method to turn on different cases - but I'm not sure how to go about this and the documentation is ambiguous. In nested objects, does the value returned from default go by key, or is it just a generic include/discard that looks at the whole object? How does that method accommodate nested values? I've looked through previous questions and can't seem to find the best approach to case-specific encoding (which unfortunately seems like what I'm going to need to do here).
我将Raymond Hettinger的解决方案改编为python 3。
以下是改变的地方:
unicode消失了
用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')]
如果你只需要编码集合,而不是一般的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
您可以创建一个自定义编码器,在遇到集合时返回一个列表。这里有一个例子:
import json
class SetEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
data_str = json.dumps(set([1,2,3,4,5]), cls=SetEncoder)
print(data_str)
# Output: '[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
这表明编码器将接受返回的列表结果,并递归地调用其子序列化器。为多个类型添加自定义序列化器,可以这样做:
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)
data_str = json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
print(data_str)
# Output: '[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'
JSON表示法只有少数几种原生数据类型(对象、数组、字符串、数字、布尔值和null),因此JSON中序列化的任何东西都需要表示为这些类型之一。
如json模块文档所示,这种转换可以由JSONEncoder和JSONDecoder自动完成,但这样你就会放弃一些你可能需要的其他结构(如果你将集合转换为列表,那么你就失去了恢复常规列表的能力;如果使用dict.fromkeys(s)将集合转换为字典,则失去恢复字典的能力)。
更复杂的解决方案是构建一个可以与其他原生JSON类型共存的自定义类型。这让你可以存储嵌套结构,包括列表,集,字典,小数,datetime对象等:
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模块)可能会有用。它们各自支持更大范围的数据类型。