我有一个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
返回的值是否按键,或者它只是查看整个对象的通用包含/丢弃?该方法如何适应嵌套值?我已经查看过以前的问题,似乎无法找到针对特定情况编码的最佳方法(不幸的是,这似乎是我需要在这里做的)。
答案 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')]
或者,使用更通用的序列化技术(如YAML,Twisted 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时,这些字段将保留为数组。如果要保留类型,则需要编写自定义编码器。