JSON序列化以元组为键的字典
Posted
技术标签:
【中文标题】JSON序列化以元组为键的字典【英文标题】:JSON serialize a dictionary with tuples as key 【发布时间】:2011-10-23 12:23:18 【问题描述】:在 Python 中有没有办法序列化使用元组作为键的字典?
例如
a = (1, 2): 'a'
仅使用json.dumps(a)
会引发此错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/json/__init__.py", line 230, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.6/json/encoder.py", line 367, in encode
chunks = list(self.iterencode(o))
File "/usr/lib/python2.6/json/encoder.py", line 309, in _iterencode
for chunk in self._iterencode_dict(o, markers):
File "/usr/lib/python2.6/json/encoder.py", line 268, in _iterencode_dict
raise TypeError("key 0!r is not a string".format(key))
TypeError: key (1, 2) is not a string
【问题讨论】:
Best way to encode tuples with json 的可能重复项 【参考方案1】:你不能将它序列化为 json,json 对于什么是 dict 键的理解比 python 灵活得多。
您可以将映射转换为一系列键值对,如下所示:
import json
def remap_keys(mapping):
return ['key':k, 'value': v for k, v in mapping.iteritems()]
...
json.dumps(remap_keys((1, 2): 'foo'))
>>> '["value": "foo", "key": [1, 2]]'
【讨论】:
您能否在加载 JSON 后添加代码以取消重新映射键?这样可以得到更完整的答案。 请注意 Python 3 更名为dict.iteritems
-> dict.items
@kvothe 我添加了一个答案,也解决了您的要求【参考方案2】:
from json import loads, dumps
from ast import literal_eval
x = (0, 1): 'la-la la', (0, 2): 'extricate'
# save: convert each tuple key to a string before saving as json object
s = dumps(str(k): v for k, v in x.items())
# load in two stages:
# (i) load json object
obj = loads(s)
# (ii) convert loaded keys from string back to tuple
d = literal_eval(k): v for k, v in obj.items()
见https://***.com/a/12337657/2455413。
【讨论】:
谢谢! literal_eval 很有帮助! 这也适用于作为键的字典!或者literal_eval(str(x)) == x
的任何地方!请注意,literal_eval()
存在安全风险,它将执行任意代码,因此只有在您信任正在加载的 JSON 字符串时才使用它。您可以通过在保存时替换 str(k)
=> json.dumps(k)
和加载时替换 literal_eval(k)
=> tuple(json.loads(k))
来避免这种情况。【参考方案3】:
JSON 仅支持字符串作为键。您需要选择一种方式将这些元组表示为字符串。
【讨论】:
就问题而言,这并不完全正确。映射到 JSON 键的 Python 类型必须是 str、int、float、bool 或 None,因此 OP 只需要弄清楚如何映射到其中一种类型【参考方案4】:您可以只使用 str((1,2))
作为键,因为 json 只期望键作为字符串,但如果您使用它,则必须使用 a[str((1,2))]
来获取值。
【讨论】:
如果我们想保留我们键入项目的方式,我认为这是最好的选择。【参考方案5】:json 只能接受字符串作为 dict 的键, 你可以做的是用这样的字符串替换元组键
with open("file", "w") as f:
k = dic.keys()
v = dic.values()
k1 = [str(i) for i in k]
json.dump(json.dumps(dict(zip(*[k1,v]))),f)
当您想阅读它时,您可以使用
将键更改回元组with open("file", r) as f:
data = json.load(f)
dic = json.loads(data)
k = dic.keys()
v = dic.values()
k1 = [eval(i) for i in k]
return dict(zip(*[k1,v]))
【讨论】:
【参考方案6】:这是一种方法。在解码主字典并重新排序整个字典后,需要对密钥进行json解码,但这是可行的:
import json
def jsonEncodeTupleKeyDict(data):
ndict = dict()
# creates new dictionary with the original tuple converted to json string
for key,value in data.iteritems():
nkey = json.dumps(key)
ndict[nkey] = value
# now encode the new dictionary and return that
return json.dumps(ndict)
def main():
tdict = dict()
for i in range(10):
key = (i,"data",5*i)
tdict[key] = i*i
try:
print json.dumps(tdict)
except TypeError,e:
print "JSON Encode Failed!",e
print jsonEncodeTupleKeyDict(tdict)
if __name__ == '__main__':
main()
我不声称这种方法的任何效率。我需要它来将一些操纵杆映射数据保存到文件中。我想使用一些可以创建半人类可读格式的东西,以便在需要时对其进行编辑。
【讨论】:
【参考方案7】:您实际上不能将元组序列化为 json 的键,但您可以在反序列化文件后将元组转换为字符串并恢复它。
with_tuple = (0.1, 0.1): 3.14 ## this will work in python but is not serializable in json
(0.1, 0.1): 3.14
但是你不能用 json 序列化它。但是,您可以使用
with_string = str((0.1, 0.1))[1:-1]: 3.14 ## the expression [1,-1] removes the parenthesis surrounding the tuples in python.
'0.1, 0.1': 3.14 # This is serializable
稍微作弊,您将通过分别处理每个键(作为 str)来恢复原始元组(在反序列化整个文件之后)
tuple(json.loads("["+'0.1, 0.1'+"]")) ## will recover the tuple from string
(0.1, 0.1)
使用json.loads
将字符串转换为元组有点过载,但它会起作用。封装它就完成了。
和平愉快的编码!
尼古拉斯
【讨论】:
【参考方案8】:这个解决方案:
规避eval()
的安全风险。
很短。
可作为保存和加载功能进行复制粘贴。
保留元组的结构作为键,以防您手动编辑 JSON。
将丑陋的\"
添加到元组表示中,这比这里的其他str()
/eval()
方法更糟糕。
只能将元组作为嵌套字典的第一级键处理(在撰写本文时,这里没有其他解决方案可以做得更好)
def json_dumps_tuple_keys(mapping):
string_keys = json.dumps(k): v for k, v in mapping.items()
return json.dumps(string_keys)
def json_loads_tuple_keys(string):
mapping = json.loads(string)
return tuple(json.loads(k)): v for k, v in mapping.items()
m = (0,"a"): "first", (1, "b"): [9, 8, 7]
print(m) # (0, 'a'): 'first', (1, 'b'): [9, 8, 7]
s = json_dumps_tuple_keys(m)
print(s) # "[0, \"a\"]": "first", "[1, \"b\"]": [9, 8, 7]
m2 = json_loads_tuple_keys(s)
print(m2) # (0, 'a'): 'first', (1, 'b'): [9, 8, 7]
print(m==m2) # True
【讨论】:
【参考方案9】:这是一个完整的示例,用于将带有元组键和值的嵌套字典编码/解码到 json 中。元组键将是 JSON 中的字符串。
tuple 或 set 类型的值 将被转换为 list
def JSdecoded(item:dict, dict_key=False):
if isinstance(item, list):
return [ JSdecoded(e) for e in item ]
elif isinstance(item, dict):
return literal_eval(key) : value for key, value in item.items()
return item
def JSencoded(item, dict_key=False):
if isinstance(item, tuple):
if dict_key:
return str(item)
else:
return list(item)
elif isinstance(item, list):
return [JSencoded(e) for e in item]
elif isinstance(item, dict):
return JSencoded(key, True) : JSencoded(value) for key, value in item.items()
elif isinstance(item, set):
return list(item)
return item
用法
import json
pydata = [
('Apple','Green') : "Tree",
('Orange','Yellow'):"Orchard",
('John Doe', 1945) : "New York"
]
jsstr= json.dumps(JSencoded(pydata), indent='\t')
print(jsstr)
#[
#
# "('Apple', 'Green')": "Tree",
# "('Orange', 'Yellow')": "Orchard",
# "('John Doe', 1945)": "New York"
#
#]
data = json.loads(jsstr) #string keys
newdata = JSdecoded(data) #tuple keys
print(newdata)
#[('Apple', 'Green'): 'Tree', ('Orange', 'Yellow'): 'Orchard', ('John Doe', 1945): 'New York']
【讨论】:
这很好!如果您需要练习递归,请尝试解决此问题!需要明确的是,这可以处理嵌套字典,但只有第一级键可以是元组。如果较低级别的键是元组,则在 JSdecoded 中它们不会被评估回元组。【参考方案10】:您可以使用以下两个函数将 dict_have_tuple_as_key 转换为 json_array_having_key_and_value_as_keys,然后再将其反转换
import json
def json_dumps_dict_having_tuple_as_key(dict_having_tuple_as_key):
if not isinstance(dict_having_tuple_as_key, dict):
raise Exception('Error using json_dumps_dict_having_tuple_as_key: The input variable is not a dictionary.')
list_of_dicts_having_key_and_value_as_keys = ['key': k, 'value': v for k, v in dict_having_tuple_as_key.items()]
json_array_having_key_and_value_as_keys = json.dumps(list_of_dicts_having_key_and_value_as_keys)
return json_array_having_key_and_value_as_keys
def json_loads_dictionary_split_into_key_and_value_as_keys_and_underwent_json_dumps(json_array_having_key_and_value_as_keys):
list_of_dicts_having_key_and_value_as_keys = json.loads(json_array_having_key_and_value_as_keys)
if not all(['key' in diz for diz in list_of_dicts_having_key_and_value_as_keys]) and all(['value' in diz for diz in list_of_dicts_having_key_and_value_as_keys]):
raise Exception('Error using json_loads_dictionary_split_into_key_and_value_as_keys_and_underwent_json_dumps: at least one dictionary in list_of_dicts_having_key_and_value_as_keys ismissing key "key" or key "value".')
dict_having_tuple_as_key =
for dict_having_key_and_value_as_keys in list_of_dicts_having_key_and_value_as_keys:
dict_having_tuple_as_key[ tuple(dict_having_key_and_value_as_keys['key']) ] = dict_having_key_and_value_as_keys['value']
return dict_having_tuple_as_key
用法示例:
my_dict =
('1', '1001', '2021-12-21', '1', '484'): "name": "Carl", "surname": "Black", "score": 0,
('1', '1001', '2021-12-22', '1', '485'): "name": "Joe", "id_number": 134, "percentage": 11
my_json = json_dumps_dict_having_tuple_as_key(my_dict)
print(my_json)
['key': ['1', '1001', '2021-12-21', '1', '484'], 'value': 'name': 'Carl', 'surname': 'Black', 'score': 0, 'key': ['1', '1001', '2021-12-22', '1', '485'], 'value': 'name': 'Joe', 'id_number': 134, 'percentage': 11]
my_dict_reconverted = json_loads_dictionary_split_into_key_and_value_as_keys_and_underwent_json_dumps(my_json)
print(my_dict_reconverted)
('1', '1001', '2021-12-21', '1', '484'): 'name': 'Carl', 'surname': 'Black', 'score': 0, ('1', '1001', '2021-12-22', '1', '485'): 'name': 'Joe', 'id_number': 134, 'percentage': 11
# proof of working 1
my_dict == my_dict_reconverted
True
# proof of working 2
my_dict == json_loads_dictionary_split_into_key_and_value_as_keys_and_underwent_json_dumps(
json_dumps_dict_having_tuple_as_key(my_dict)
)
True
(使用@SingleNegationElimination 表达的概念来回答@Kvothe 评论)
【讨论】:
以上是关于JSON序列化以元组为键的字典的主要内容,如果未能解决你的问题,请参考以下文章