如何JSON序列化集?

我有一个Python set ,其中包含__hash____eq__方法的对象,以便确保集合中不包含重复项。

我需要json对这个结果set进行编码,但是将一个空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 

我知道我可以创build一个具有自定义default方法的json.JSONEncoder类的扩展,但我甚至不知道从哪里开始转换set 。 我应该在默认方法中的set值之外创build一个字典,然后返回编码? 理想情况下,我想使默认的方法能够处理所有原始的编码器扼stream器上的数据types(我使用Mongo作为数据源,所以date似乎也引发这个错误)

任何提示正确的方向将不胜感激。

编辑:

感谢你的回答! 也许我应该更精确。

我利用(和upvoted)在这里的答案来解决被翻译的set的局限性,但也有内部的关键是一个问题。

集合中的对象是转化为__dict__复杂对象,但是它们本身也可以包含其属性的值,这些属性可能不适用于json编码器中的基本types。

有很多不同的types进入这个set ,哈希函数基本上为实体计算一个唯一的ID,但是按照NoSQL真正的精神,没有确切地告诉子对象包含什么。

一个对象可能包含一个startsdate值,而另一个可能有其他一些架构,其中不包含包含“非原始”对象的键。

这就是为什么我能想到的唯一解决scheme是扩展JSONEncoder来replacedefault方法来打开不同的情况 – 但我不知道如何去做这个和文档是不明确的。 在嵌套对象中,从default返回的值是按键,还是只是一个generics包含/放弃,看整个对象? 该方法如何适应嵌套值? 我已经查看了以前的问题,似乎无法find特定于案例的编码的最佳方法(不幸的是,这似乎是我需要在这里做的)。

JSON表示法只有less数本地数据types(对象,数组,string,数字,布尔值和null),所以在JSON中序列化的任何东西都需要表示为这些types之一。

如json模块docs中所示 ,这个转换可以通过JSONEncoderJSONDecoder自动完成,但是您将放弃一些您可能需要的其他结构(如果将集合转换为列表,那么您将失去恢复正常列表;如果您使用dict.fromkeys(s)将集合转换为字典,那么您将失去恢复字典的能力)。

更复杂的解决scheme是构build可以与其他本机JSONtypes共存的自定义types。 这使您可以存储包含列表,集合,字典,小数点,date时间对象等的嵌套结构:

 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模块可能是有用的。 这些都支持更大范围的数据types。

您可以创build一个自定义编码器,当它遇到一个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]' 

你也可以用这种方式检测其他types。 如果您需要保留该列表实际上是一个集合,您可以使用自定义编码。 像return {'type':'set', 'list':list(obj)}可能工作。

为了说明嵌套types,考虑序列化这个:

 >>> 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结果,并recursion调用其子节点上的序列化程序。 要为多种types添加自定义序列化程序,可以这样做:

 >>> 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"]' 

只有字典,列表和原始对象types(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 

我将Raymond Hettinger的解决scheme改编为python 3。

这是什么改变了:

  • unicode消失了
  • 更新呼叫父母的defaultsuper()
  • 使用base64bytestypes序列化为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')]