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 中的字符串。

tupleset 类型的

将被转换为 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序列化以元组为键的字典的主要内容,如果未能解决你的问题,请参考以下文章

从以元组为键的字典中获取熊猫数据框

如何在 Scala 中以元组为键合并 Maps

用元组作为键的字典访问键值[重复]

如何将带有元组键的 python 字典转换为 pandas 多索引数据框?

Python:元组/字典作为键、选择、排序

Python字典,作为不增加值的元组键