如何JSON序列化集合?

2024-04-19 11:53:26 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个Pythonset,它包含具有__hash____eq__方法的对象,以确保集合中不包含重复项。

我需要json对这个结果进行编码set,但是即使向json.dumps方法传递空的set也会引发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类的扩展,但我甚至不知道从何处开始转换set。我应该用默认方法中的set值创建一个字典,然后返回该值的编码吗?理想情况下,我希望默认方法能够处理原始编码器阻塞的所有数据类型(我使用Mongo作为数据源,因此日期似乎也会引发此错误)

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

编辑:

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

我在这里使用(并向上投票)答案来绕过被翻译的set的限制,但也有一些内部键是一个问题。

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

这个set中有很多不同的类型,散列基本上为实体计算一个唯一的id,但是根据NoSQL的真正精神,无法确切地知道子对象包含什么。

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

这就是为什么我能想到的唯一解决方案是扩展JSONEncoder来替换default方法来打开不同的情况-但我不确定如何进行此操作,而且文档是不明确的。在嵌套对象中,从default返回的值是按键返回的,还是只是查看整个对象的泛型include/discard?该方法如何适应嵌套值?我已经浏览了前面的问题,似乎找不到针对具体情况的编码的最佳方法(不幸的是,这似乎是我需要在这里做的)。


Tags: 对象方法inpyjsondefault编码encoder
3条回答

我将Raymond Hettinger's solution改编为python 3。

以下是改变的地方:

  • unicode消失
  • super()更新了对父级default的调用
  • 使用base64bytes类型序列化为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表示法只有少数本机数据类型(对象、数组、字符串、数字、布尔值和空值),因此任何在JSON中序列化的数据都需要表示为这些类型之一。

json module docs所示,这种转换可以由JSONEncoderJSONDecoder自动完成,但随后您将放弃一些可能需要的其他结构(如果将集合转换为列表,则将失去恢复常规列表的能力;如果使用dict.fromkeys(s)将集合转换为字典,则将失去恢复字典的能力)。

一个更复杂的解决方案是构建一个可以与其他本地JSON类型共存的自定义类型。这允许您存储嵌套结构,其中包括列表、集合、dict、小数、datetime对象等:

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

下面是一个示例会话,显示它可以处理列表、dict和set:

>>> 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 module。它们都支持更大范围的数据类型。

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

相关问题 更多 >