json.dump()抛出“TypeError: keys must be a string”错误。

13

我有一些非常简单的代码,它接受一个将元组作为键的字典,并将其转换为json格式:

In [11]:

import simplejson as json
In [12]:

data = {('category1', 'category2'): 4}
In [13]:

json.dumps(data)

然而运行代码后,我得到了:

TypeError: keys must be a string

我已经尝试使用 str() 函数将键和其他所有找到的东西转换为字符串,但都没有成功。


5
你了解 JSON 格式吗?它不允许使用类似 ('category1', 'category2') 的键,实际上,它根本不支持元组。因此,你需要重新组织你的数据。 - user2357112
你可以通过创建字符串来从元组中进行hack,例如{"category1#category2":4}。JSON本身不支持将元组作为键。 - Aditya
5个回答

9
错误信息很明显:你的键必须是字符串。
如果我使用你提供的示例,并将键转换为 str() 类型:
>>> data = {str(('category1', 'category2')): 4}

它完全正常运作:

>>> json.dumps(data)
'{"(\'category1\', \'category2\')": 4}'

话虽如此,在您的位置上,我会考虑让您的密钥更易读。可能是这样的:

>>> data = dict((':'.join(k), v) for k,v in data.items())

这将把一个键值对如 ('category1', 'category2') 转换为 category1:category2


7
如果这个答案能解释如何恢复原始对象就太好了。json序列化是一个双向操作...仅执行dumps是不够的,还需要知道如何执行loads - Dr_Zaszuś
1
我不确定:问题并没有明确需要哪种数据结构。字符串化的键是否足够?那么可以简单地在JSON数据上调用loads即可完成。如果数据结构需要元组作为键,则解决方案将会有所不同。话虽如此,如果您对反序列化JSON有特定的问题,请随时提出新问题,我们很乐意帮助您。 - larsks

4
我知道Python之禅中提到:

扁平优于嵌套。

但是,在我的情境中,我有一些嵌套字典。因此,我对其进行了递归处理:
import json

def key_to_json(data):
    if data is None or isinstance(data, (bool, int, str)):
        return data
    if isinstance(data, (tuple, frozenset)):
        return str(data)
    raise TypeError

def to_json(data):
    if data is None or isinstance(data, (bool, int, tuple, range, str, list)):
        return data
    if isinstance(data, (set, frozenset)):
        return sorted(data)
    if isinstance(data, dict):
        return {key_to_json(key): to_json(data[key]) for key in data}
    raise TypeError

data = {('category1', 'category2'): {frozenset(['cat1', 'cat2']): 1212}}
json.dumps(to_json(data))
# '{"(\'category1\', \'category2\')": {"frozenset({\'cat2\', \'cat1\'})": 1212}}'

调整此代码以适应您的上下文。


3
请尝试以下方法。在将字典转换为json之前,将其键值设为字符串而非元组。当将json转换回字典时,请将其再次转换为列表/元组。
data  =['category1', 'category2']
data_map = {}
data_map[','.join(data)]= 4
json.dumps(data_map)

不要忘记在将JSON转换为字典后再将字符串转回列表。

如何在读取JSON文件时将字符串转换为元组? - Ash

2
如@starriet所说,如果您的目标是在不丢失数据的情况下进行序列化和反序列化,则应查看pickle
但是,如果您只想打印调试/人类可读性的东西,请检查stringify方法:
import json
import numpy as np
from collections.abc import Iterable

encode = json.JSONEncoder().encode


def cast_key(key):
    "Cast dictionary key"
    try:
        encode({key: 123})
        return key
    except:
        pass

    if isinstance(key, tuple):
        return encode(key)

    return str(key)


def cast_value(value):
    "Cast dictionary value"
    try:
        encode(value)
        return value
    except TypeError:
        pass

    try:
        if np.issubdtype(value, np.integer):
            return int(value)
    except ValueError:
        pass

    return str(value)


def coerce_types(arg):
    if isinstance(arg, dict):
        obj = {}
        for k, v in arg.items():
            k = cast_key(k)
            if isinstance(v, dict):
                v = coerce_types(v)
            else:
                v = cast_value(v)
            obj[k] = v
        return obj
    elif isinstance(arg, Iterable):
        return [coerce_types(e) for e in arg]
    else:
        return cast_value(arg)


def stringify(obj):
    # First try default serializer
    try:
        return json.dumps(obj)
    except TypeError:
        pass

    # Default failed, so we coerce types and try again
    obj_prep = coerce_types(obj)
    return json.dumps(obj_prep)

PyTest

import numpy as np
from stringify import stringify


def test_simple_object():
    input = {'foo': 'bar'}
    expected = '{"foo": "bar"}'
    actual = stringify(input)
    assert expected == actual


def test_nested_object():
    input = {'foo': {'child': 'bar'}, 'age': 20}
    expected = '{"foo": {"child": "bar"}, "age": 20}'
    actual = stringify(input)
    assert expected == actual


def test_numpy_value_int():
    input = {'foo': np.int64(123)}
    expected = '{"foo": 123}'
    actual = stringify(input)
    assert expected == actual


def test_numpy_value_float():
    input = {'foo': np.float64(123.456)}
    expected = '{"foo": 123.456}'
    actual = stringify(input)
    assert expected == actual


def test_numpy_key():
    input = {np.int64(123): 'foo'}
    expected = '{"123": "foo"}'
    actual = stringify(input)
    assert expected == actual


def test_numpy_nested_l1():
    input = {'foo': {'bar': {np.int64(123): 'foo'}}, 'age': 20}
    expected = '{"foo": {"bar": {"123": "foo"}}, "age": 20}'
    actual = stringify(input)
    assert expected == actual


def test_numpy_nested_l2():
    input = {'foo': {'bar': {'baz': {'foo': np.int64(123)}}}, 'age': 20}
    expected = '{"foo": {"bar": {"baz": {"foo": 123}}}, "age": 20}'
    actual = stringify(input)
    assert expected == actual


def test_array_int():
    input = [1, 2, 3]
    expected = '[1, 2, 3]'
    actual = stringify(input)
    assert expected == actual


def test_array_numpy_int():
    input = [np.int64(n) for n in [1, 2, 3]]
    expected = '[1, 2, 3]'
    actual = stringify(input)
    assert expected == actual


def test_array_numpy_float():
    input = [np.float64(n) for n in [1.1, 2.2, 3.3]]
    expected = '[1.1, 2.2, 3.3]'
    actual = stringify(input)
    assert expected == actual


def test_object_array():
    input = [{'foo': 'bar'}]
    expected = '[{"foo": "bar"}]'
    actual = stringify(input)
    assert expected == actual


def test_object_array_numpy():
    input = [{'foo': 'bar'}, {'bar': np.int64(123)}]
    expected = '[{"foo": "bar"}, {"bar": 123}]'
    actual = stringify(input)
    assert expected == actual


def test_tuple_value():
    input = {'foo': ('bar', 'baz')}
    expected = '{"foo": ["bar", "baz"]}'
    actual = stringify(input)
    assert expected == actual


def test_tuple_key():
    input = {('bar', 'baz'): 'foo'}
    expected = '{"[\\"bar\\", \\"baz\\"]": "foo"}'
    actual = stringify(input)
    assert expected == actual


1

如果您的目的是“保存”,只需使用pickle。

API几乎相同。

import pickle
data = {('category1', 'category2'): 4}
s = pickle.dumps(data) # serialized data
d = pickle.loads(s) # the original dictionary

如果你想将它保存到磁盘中,
# write
with open('file1.pkl', 'wb') as f:
    pickle.dump(data, f)

# read
with open('file1.pkl', 'rb') as f:
    data = pickle.load(f) 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接