如何JSON序列化集?

时间:2011-11-22 16:38:01

标签: python json serialization set

我有一个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方法以打开不同的情况 - 但我不知道如何解决这个问题文档含糊不清。在嵌套对象中,从default返回的值是否按键,或者它只是查看整个对象的通用包含/丢弃?该方法如何适应嵌套值?我已经查看过以前的问题,似乎无法找到针对特定情况编码的最佳方法(不幸的是,这似乎是我需要在这里做的)。

9 个答案:

答案 0 :(得分:97)

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

json module docs所示,此转换可以通过 JSONEncoder JSONDecoder 自动完成,但是您可能会放弃其他一些结构可能需要(如果您将集转换为列表,则您将失去恢复常规列表的能力;如果使用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

这是一个示例会话,显示它可以处理列表,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')]

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

答案 1 :(得分:13)

您无需创建自定义编码器类即可提供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]

答案 2 :(得分:5)

JSON中只提供字典,列表和原始对象类型(int,string,bool)。

答案 3 :(得分:4)

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

以下是改变了:

  • EditText a, b; **EIDT** Button btnLogin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); a = (EditText) findViewById(R.id.etUsername); b = (EditText) findViewById(R.id.etPassword); **EIDT** btnLogin = (Button) findViewById(R.id.bLogin); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String str = a.getText().toString(); String pass = b.getText().toString(); String password = helper.searchPass(str); if(pass.equals(password)) { Intent i = new Intent(MainActivity.this, Options.class); i.putExtra("Username",str); startActivity(i); } else { Toast temp = Toast.makeText(MainActivity.this , "Username and Password Dont Match!" , Toast.LENGTH_SHORT); temp.show(); } } } 消失了
  • 使用unicode
  • 更新了对父母default的通话
  • 使用super()base64类型序列化为bytes(因为似乎python 3中的str无法转换为JSON)
bytes

答案 4 :(得分:3)

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

答案 5 :(得分:1)

accepted solution的一个缺点是其输出是特定于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密钥。显然可以随意使用另一个更深层混淆的密钥。

答案 6 :(得分:0)

@AnttiHaapala 的缩短版:

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

答案 7 :(得分:0)

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

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

只有不可序列化的数据才会被赋予为 default 的函数处理,因此只有 set 会被转换为 tuple

答案 8 :(得分:-1)

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

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

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