如何将集合序列化为 JSON?[重复]

2025-01-20 09:07:00
admin
原创
94
摘要:问题描述:我有一个set包含对象__hash__和__eq__方法的 Python,以确保集合中不包含重复项。我需要对这个结果进行 json 编码set,但是即使将一个空的结果传递set给json.dumps方法也会引发TypeError。 File "/usr/lib/python2.7/jso...

问题描述:

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

我需要对这个结果进行 json 编码set,但是即使将一个空的结果传递setjson.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方法以打开不同的案例 - 但我不知道如何做到这一点,而且文档含糊不清。在嵌套对象中,从defaultgo 返回的值是否按键,或者它只是一个查看整个对象的通用包含/丢弃?该方法如何适应嵌套值?我查看了之前的问题,似乎找不到针对特定案例的编码的最佳方法(不幸的是,这似乎是我在这里需要做的)。


解决方案 1:

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)

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

这表示编码器将获取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)
 
data_str = json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
print(data_str)
# Output: '[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'

解决方案 2:

JSON符号只有少数几种原生数据类型(对象、数组、字符串、数字、布尔值和 null),因此 JSON 中序列化的任何内容都需要表示为这些类型之一。

如json 模块文档中所示,此转换可以由JSONEncoderJSONDecoder自动完成,但这样一来,您可能需要的其他一些结构(如果将集合转换为列表,则将失去恢复常规列表的能力;如果使用将集合转换为字典,dict.fromkeys(s)则将失去恢复字典的能力)。

更复杂的解决方案是构建一个可以与其他原生 JSON 类型共存的自定义类型。这允许您存储包含列表、集合、字典、小数、日期时间对象等的嵌套结构:

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 模块)可能会有用。它们各自支持更广泛的数据类型。

解决方案 3:

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

在所有受支持的 Python 版本中均有效[1, 2, 3]

解决方案 4:

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

json.dumps({"Hello World": {1, 2}}, default=tuple)

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

解决方案 5:

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

变化的内容如下:

  • unicode消失了

  • default更新了与​​家长的通话内容super()

  • 使用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')]

解决方案 6:

如果你只需要快速转储而不想实现自定义编码器。你可以使用以下命令:

json_string = json.dumps(data, iterable_as_array=True)

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

还要确保已simplejson安装并需要。你可以在PyPi

上找到它。

解决方案 7:

JSON 中仅可用字典、列表和原始对象类型(int、string、bool)。

解决方案 8:

@AnttiHaapala 的缩写版本:

json.dumps(dict_with_sets, default=lambda x: list(x) if isinstance(x, set) else x)

解决方案 9:

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

解决方案 10:

可接受的解决方案的一个缺点是其输出非常特定于 Python。也就是说,其原始 JSON 输出无法被人类观察到,也无法被其他语言(例如 JavaScript)加载。示例:

db = {
        "a": [ 44, set((4,5,6)) ],
        "b": [ 55, set((4,3,2)) ]
        }

j = dumps(db, cls=PythonObjectEncoder)
print(j)

将会给你:

{"a": [44, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsESwVLBmWFcQJScQMu"}], "b": [55, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsCSwNLBGWFcQJScQMu"}]}

我可以提出一个解决方案,在输出时将集合降级为包含列表的字典,并在使用相同的编码器加载到 python 中时返回集合,从而保持可观察性和语言不可知性:

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)
        elif isinstance(obj, set):
            return {"__set__": list(obj)}
        return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}

def as_python_object(dct):
    if '__set__' in dct:
        return set(dct['__set__'])
    elif '_python_object' in dct:
        return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
    return dct

db = {
        "a": [ 44, set((4,5,6)) ],
        "b": [ 55, set((4,3,2)) ]
        }

j = dumps(db, cls=PythonObjectEncoder)
print(j)
ob = loads(j)
print(ob["a"])

这让你:

{"a": [44, {"__set__": [4, 5, 6]}], "b": [55, {"__set__": [2, 3, 4]}]}
[44, {'__set__': [4, 5, 6]}]

请注意,序列化包含带键元素的字典"__set__"将破坏此机制。因此__set__现在已成为保留dict键。显然,您可以随意使用其他更深层次混淆的键。

解决方案 11:

>>> import json
>>> set_object = set([1,2,3,4])
>>> json.dumps(list(set_object))
'[1, 2, 3, 4]'

解决方案 12:

你应该尝试 jsonwhatever

https://pypi.org/project/jsonwhatever/

pip 安装 jsonwhatever

from jsonwhatever import JsonWhatEver

set_a = {1,2,3}

jsonwe = JsonWhatEver()

string_res = jsonwe.jsonwhatever('set_string', set_a)

print(string_res)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用