如何JSON序列化集?

Dea*_*ado 138 python serialization json set

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

我需要对这个结果进行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
Run Code Online (Sandbox Code Playgroud)

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

任何暗示正确的方向将不胜感激.

编辑:

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

我在这里使用(和upvoted)答案来解决set被翻译的限制,但是内部键也是一个问题.

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

这里有很多不同的类型set,哈希基本上计算了实体的唯一ID,但是在NoSQL的真正精神中,没有确切知道子对象包含的内容.

一个对象可能包含日期值starts,而另一个对象可能有一些其他模式,其中不包含包含"非原始"对象的键.

这就是为什么我能想到的唯一解决方案是扩展JSONEncoder替换default方法以打开不同的情况 - 但我不知道如何解决这个问题并且文档含糊不清.在嵌套对象中,是否default通过键返回值,或者它只是查看整个对象的通用include/discard?该方法如何适应嵌套值?我已经查看过以前的问题,似乎无法找到针对特定情况编码的最佳方法(不幸的是,这似乎是我需要在这里做的).

Ray*_*ger 110

JSON表示法只有少数本机数据类型(对象,数组,字符串,数字,布尔值和null),因此在JSON中序列化的任何内容都需要表示为这些类型之一.

json模块文档所示,这种转换可以通过JSONEncoderJSONDecoder自动完成,但是你会放弃一些你可能需要的其他结构(如果你将集合转换为列表,那么你就失去了恢复常规的能力列表;如果您将集合转换为字典,dict.fromkeys(s)那么您将失去恢复字典的能力).

更复杂的解决方案是构建可与其他本机JSON类型共存的自定义类型.这使您可以存储包含列表,集合,dicts,小数,日期时间对象等的嵌套结构:

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
Run Code Online (Sandbox Code Playgroud)

这是一个示例会话,显示它可以处理列表,dicts和集合:

>>> 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')]
Run Code Online (Sandbox Code Playgroud)

或者,使用更通用的序列化技术(如YAML,Twisted Jelly或Python的pickle模块)可能很有用.这些都支持更大范围的数据类型.

  • @KarlKnechtel YAML是JSON的超集(非常接近).它还为二进制数据,集合,有序映射和时间戳添加了标记.支持更多数据类型是我所说的"更通用的".你似乎在不同的意义上使用了"通用"这个短语. (12认同)
  • 这是我第一次听说YAML比JSON更通用...... o_O (10认同)
  • 不要忘记[jsonpickle](http://github.com/jsonpickle/jsonpickle),这是一个用于将Python对象腌制为JSON的通用库,就像这个答案所暗示的那样. (3认同)
  • 从版本1.2开始,YAML是JSON的严格超集.所有合法的JSON现在都是合法的YAML.http://www.yaml.org/spec/1.2/spec.html (3认同)
  • 此代码示例导入 `JSONDecoder` 但不使用它 (2认同)

jte*_*ace 105

您可以创建一个自定义编码器,list在遇到时返回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]'
Run Code Online (Sandbox Code Playgroud)

您也可以通过这种方式检测其他类型.如果您需要保留列表实际上是一个集合,则可以使用自定义编码.return {'type':'set', 'list':list(obj)}可能有用的东西.

要说明嵌套类型,请考虑序列化:

>>> class Something(object):
...    pass
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
Run Code Online (Sandbox Code Playgroud)

这会引发以下错误:

TypeError: <__main__.Something object at 0x1691c50> is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

这表示编码器将获取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"]'
Run Code Online (Sandbox Code Playgroud)

  • @jterrace 有什么想法可以在 json.loads 时恢复此恢复(要设置的列表)吗?就像在“SetEncoder”期间编码此信息或其他内容? (2认同)

Ant*_*ala 23

您不需要创建自定义编码器类来提供该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)
Run Code Online (Sandbox Code Playgroud)

导致[1, 2, 3]所有受支持的 Python 版本。

  • @BerryTsakala 但 json 对象不能将整数作为键...... (2认同)

Tom*_*ohl 20

如果您确定唯一的不可序列化数据将是sets,那么有一个非常简单(且肮脏)的解决方案:

json.dumps({"Hello World": {1, 2}}, default=tuple)
Run Code Online (Sandbox Code Playgroud)

只有不可序列化的数据才会使用给定的函数进行处理default,因此只有set会被转换为 a tuple

  • `json.dumps({"Hello World": {1, 2}}, default=list)` 也有效 (8认同)

Dav*_*vák 8

如果您只需要快速转储并且不想实现自定义编码器。您可以使用以下内容:

json_string = json.dumps(data, iterable_as_array=True)
Run Code Online (Sandbox Code Playgroud)

这会将所有集合(和其他可迭代对象)转换为数组。请注意,当您解析回 JSON 时,这些字段将保留为数组。如果你想保留类型,你需要编写自定义编码器。

还要确保已simplejson安装并需要。您可以在PyPi
上找到它。

  • 当我尝试此操作时,我得到: TypeError: __init__() got an Unexpected keywords argument 'iterable_as_array' (13认同)
  • import simplejson as json 然后 json_string = json.dumps(data, iterable_as_array=True) 在 Python 3.6 中运行良好 (3认同)

sim*_*lmx 6

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

这是发生了什么变化:

  • unicode 消失了
  • 更新调用父母defaultsuper()
  • 使用base64序列化bytes型成str(因为它似乎bytes在python 3不能被转换为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')]
Run Code Online (Sandbox Code Playgroud)

  • [此答案](http://stackoverflow.com/a/18561055/355230)结尾处显示的代码通过[仅]对字节对象`json.dumps()进行解码和编码来完成相同的操作。 `会从'latin1'返回,或从'latin1'返回,跳过不必要的base64内容。 (3认同)

upt*_*you 6

@AnttiHaapala 的缩短版本:

json.dumps(dict_with_sets, default=lambda x: list(x) if isinstance(x, set) else x)
Run Code Online (Sandbox Code Playgroud)


Jos*_*ech 5

JSON中仅字典,列表和原始对象类型(int,字符串,布尔)可用。

  • 在谈论Python时,“原始对象类型”毫无意义。“内置对象”更有意义,但是在这里太宽泛了(对于初学者:它包括字典,列表和集合)。(不过,JSON术语可能有所不同。) (5认同)

Nei*_*ais 5

如果您只需要对集合进行编码,而不是一般的 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
Run Code Online (Sandbox Code Playgroud)