JSON 序列化以元组为键的字典

发布于 2024-11-28 14:48:30 字数 773 浏览 2 评论 0原文

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

Is there a way in Python to serialize a dictionary that is using a tuple as key?

e.g.

a = {(1, 2): 'a'}

simply using json.dumps(a) raises this error:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

冷清清 2024-12-05 14:48:30

你不能将其序列化为 json,与 python 相比,json 关于什么算作 dict 键的灵活性要低得多。

您可以将映射转换为一系列键、值对,如下所示:

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]}]'

You can't serialize that as json, json has a much less flexible idea about what counts as a dict key than python.

You could transform the mapping into a sequence of key, value pairs, something like this:

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]}]'
南城追梦 2024-12-05 14:48:30
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://stackoverflow.com/a/12337657/2455413

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()}

See https://stackoverflow.com/a/12337657/2455413.

娇女薄笑 2024-12-05 14:48:30

您可以只使用 str((1,2)) 作为键,因为 json 仅期望键作为字符串,但如果您使用它,则必须使用 a[str((1, 2))] 获取值。

You could just use str((1,2)) as key because json only expects the keys as strings but if you use this you'll have to use a[str((1,2))] to get the value.

如若梦似彩虹 2024-12-05 14:48:30

JSON 仅支持字符串作为键。您需要选择一种将这些元组表示为字符串的方法。

JSON only supports strings as keys. You'll need to choose a way to represent those tuples as strings.

匿名。 2024-12-05 14:48:30

该解决方案:

  • 避免了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

This solution:

  • Avoids the security risk of eval().
  • Is short.
  • Is copy-pastable as save and load functions.
  • Keeps the structure of tuple as the key, in case you are editing the JSON by hand.
  • Adds ugly \" to the tuple representation, which is worse than the other str()/eval() methods here.
  • Can only handle tuples as keys at the first level for nested dicts (as of this writing no other solution here can do better)
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
狼性发作 2024-12-05 14:48:30

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])) 

json can only accept strings as keys for dict,
what you can do, is to replace the tuple keys with string like so

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) 

And than when you want to read it, you can change the keys back to tuples using

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])) 
谁的新欢旧爱 2024-12-05 14:48:30

这是一种方法。它将要求在主字典解码和整个字典重新排序后对密钥进行 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()

我不声明此方法有任何效率。我需要这个来将一些操纵杆映射数据保存到文件中。我想使用一些可以创建半人类可读格式的东西,以便在需要时可以对其进行编辑。

Here is one way to do it. It will require the key to be json decoded after the main dictionary is decoded and the whole dictionary re-sequenced, but it is doable:

    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()

I make no claim to any efficiency of this method. I needed this for saving some joystick mapping data to a file. I wanted to use something that would create a semi-human readable format so it could be edited if needed.

撩起发的微风 2024-12-05 14:48:30

您可以使用以下两个函数将 dict_having_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': '卡尔' , '姓氏': '黑', '分数': 0}}, 
 {'key': ['1', '1001', '2021-12-22', '1', '485'], 'value': {'name': 'Joe', 'id_number': 134, “百分比”: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'): {'姓名': '卡尔', '姓氏': '布莱克', “分数”:0}, 
 ('1', '1001', '2021-12-22', '1', '485'): {'name': 'Joe', 'id_number': 134, 'percentage': 11}}
# proof of working 1

my_dict == my_dict_reconverted

<前><代码>正确

# 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)
)

<前><代码>正确

(使用@SingleNegationElimination表达的概念来回答@Kvothe评论)

Here are two functions you could use to convert a dict_having_tuple_as_key into a json_array_having_key_and_value_as_keys and then de-convert it the way back

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

usage example:

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

(Using concepts expressed by @SingleNegationElimination to answer @Kvothe comment)

浮光之海 2024-12-05 14:48:30

实际上,您不能将元组序列化为 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,但它会起作用。将其封装起来就完成了。

平静下来,快乐编码!

尼古拉斯

You can actually not serialize tuples as key to json, but you can convert the tuple to a string and recover it, after you have deserialized the file.

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}

But you cannot serialize it with json. However, you can use

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

With a bit of cheating, you will recover the original tuple (after having deserialized the whole file) by treating each key (as str) separately

tuple(json.loads("["+'0.1, 0.1'+"]")) ## will recover the tuple from string
(0.1, 0.1)

It is a bit of overload to convert a string to a tuple using json.loads, but it will work. Encapsulate it and you are done.

Peace out and happy coding!

Nicolas

哆兒滾 2024-12-05 14:48:30

下面是一个完整的示例,用于将带有元组键和值的嵌套字典编码到 json 中/从 json 中解码。元组键将是 JSON 中的字符串。

tupleset 类型的将转换为列表

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'}]

Here's a complete example to encode/decode nested dictionaries with tuple keys and values into/from json. tuple key will be a string in JSON.

values of types tuple or set will be converted to 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

usage

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'}]
[旋木] 2024-12-05 14:48:30
def stringify_keys(d):
    if isinstance(d, dict):
        return {str(k): stringify_keys(v) for k, v in d.items()}
    if isinstance(d, (list, tuple)):
        return type(d)(stringify_keys(v) for v in d)
    return d

json.dumps(stringify_keys(mydict))
def stringify_keys(d):
    if isinstance(d, dict):
        return {str(k): stringify_keys(v) for k, v in d.items()}
    if isinstance(d, (list, tuple)):
        return type(d)(stringify_keys(v) for v in d)
    return d

json.dumps(stringify_keys(mydict))
厌倦 2024-12-05 14:48:30

其实还有更简单的方法,不过需要修改原来的字典。

这个想法是要进入一种不同的心态。使用元组作为键与嵌套字典相同,您可以在嵌套字典中进行两次查找。具体来说,它在功能上相当于将字典转换为

a={1: {2: 'a'}}

这可以直接 jasonified,无需任何特殊处理或使用 eval

There’s actually a simpler way, though it requires changing the original dictionary.

The idea is to get into a different mindset. Using a tuple as key is the same as a nested dictionary where you do two lookups instead. Specifically, it’s functionally equivalent to transform the dictionary to

a={1: {2: 'a'}}

This can be directly jasonified without any special treatment or the usage of eval.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文