如何对集合进行JSON序列化?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (52)

我有一个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

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

为了解决set被翻译的局限性,我在这里利用(并提出了答案),但也有一些内部密钥也是一个问题。

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

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

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

这就是为什么我能想到的唯一解决方案是扩展JSONEncoder替换default方法来打开不同的案例 - 但我不知道如何去做这件事,文档是不明确的。在嵌套对象中,defaultkey是否返回值,还是仅仅是查看整个对象的泛型include / discard?该方法如何适应嵌套值?我查看了以前的问题,似乎无法找到特定于案例的编码的最佳方法(不幸的是,这看起来像我在这里需要做的)。

提问于
用户回答回答于

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

正如json模块文档所示,这种转换可以通过JSONEncoderJSONDecoder自动完成,但是可能会放弃一些可能需要的其他结构(如果将集合转换为列表,那么将失去恢复常规的能力列表;如果你将集合转换成字典,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模块可能会很有用。这些都支持更大范围的数据类型。

用户回答回答于

可以创建一个自定义编码器,以便list在遇到a时返回一个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"]'

扫码关注云+社区