当键是非平凡对象时,如何将Python字典转储到JSON?

pdkcd3nj  于 2023-02-20  发布在  Python
关注(0)|答案(5)|浏览(144)
import datetime, json
x = {'alpha': {datetime.date.today(): 'abcde'}}
print json.dumps(x)

由于JSON对象的键必须是字符串,所以上面的代码在TypeError上失败。json.dumps函数有一个名为default的参数,当JSON对象的 value 引发TypeError时,该参数会被调用,但似乎没有办法对 key 执行此操作。解决此问题的最佳方法是什么?

xqk2d5yq

xqk2d5yq1#

你可以扩展json.JSONEncoder来创建你自己的编码器,它将能够处理datetime.datetime对象(或者你想要的任何类型的对象),这样就可以创建一个字符串,这个字符串可以被复制为一个新的datetime.datetime示例。我相信这应该和让json.JSONEncoder在你的datetime.datetime示例上调用repr()一样简单。
json模块文档中描述了如何执行此操作的过程。
json模块检查它需要编码的每个值的类型,默认情况下,它只知道如何处理dict、list、tuple、strs、unicode对象、int、long、float、boolean和none:-)
JSONEncoder的skipkeys参数对您来说也很重要。
在阅读了您的评论之后,我得出结论,让JSONEncoder使用自定义函数对字典的键进行编码并不是一个简单的解决方案。如果您感兴趣,可以查看源代码和方法iterencode(),该方法调用_iterencode(),该方法调用_iterencode_dict(),这是引发类型错误的地方。
对你来说最简单的方法是用等格式键创建一个新的dict,如下所示:

import datetime, json

D = {datetime.datetime.now(): 'foo',
     datetime.datetime.now(): 'bar'}

new_D = {}

for k,v in D.iteritems():
  new_D[k.isoformat()] = v

json.dumps(new_D)

返回“{“2010-09- 15 T23:24:36.169710”:“foo”,“2010年9月15日23:24:36.169723”:“bar”}“。为了精确起见,请将其 Package 在函数中:-)

r7s23pms

r7s23pms2#

http://jsonpickle.github.io/可能是你想要的,当面临类似的问题时,我最终做了:

to_save = jsonpickle.encode(THE_THING, unpicklable=False, max_depth=4, make_refs=False)
zqdjd7g9

zqdjd7g93#

你可以做x = {'alpha': {datetime.date.today().strftime('%d-%m-%Y'): 'abcde'}}

nkkqxpd9

nkkqxpd94#

如果你真的需要这样做,你可以monkeypatch json.encoder:

from _json import encode_basestring_ascii  # used when ensure_ascii=True (which is the default where you want everything to be ascii)
from _json import encode_basestring  # used in any other case

def _patched_encode_basestring(o):
    """
    Monkey-patching Python's json serializer so it can serialize keys that are not string!
    You can monkey patch the ascii one the same way.
    """
    if isinstance(o, MyClass):
        return my_serialize(o)
    return encode_basestring(o)

json.encoder.encode_basestring = _patched_encode_basestring
rsaldnfx

rsaldnfx5#

JSON只接受这里提到的数据类型进行编码。正如@supakeen提到的,你可以扩展JSONEncoder类,以便对字典中的任何值进行编码,但不对键进行编码!如果你想对键进行编码,你必须自己做。
我使用了一个递归函数,以便将元组键编码为字符串,并在以后恢复它们。
下面是一个例子:

def _tuple_to_string(obj: Any) -> Any:
"""Serialize tuple-keys to string representation. A tuple wil be obtain a leading '__tuple__' string and decomposed in list representation.

Args:
    obj (Any): Typically a dict, tuple, list, int, or string.

Returns:
    Any: Input object with serialized tuples.
"""
# deep copy object to avoid manipulation during iteration
obj_copy = copy.deepcopy(obj)
# if the object is a dictionary
if isinstance(obj, dict):
    # iterate over every key
    for key in obj:
        # set for later to avoid modification in later iterations when this var does not get overwritten
        serialized_key = None
        # if key is tuple
        if isinstance(key, tuple):
            # stringify the key
            serialized_key = f"__tuple__{list(key)}"
            # replace old key with encoded key
            obj_copy[serialized_key] = obj_copy.pop(key)
        # if the key was modified
        if serialized_key is not None:
            # do it again for the next nested dictionary
            obj_copy[serialized_key] = _tuple_to_string(obj[key])
        # else, just do it for the next dictionary
        else:
            obj_copy[key] = _tuple_to_string(obj[key])
return obj_copy

这会将("blah", "blub")的元组转换为"__tuple__["blah", "blub"]",这样你就可以使用json.dumps()json.dump()来转储它,你可以在解码过程中使用前导"__tuple"__来检测它们,因此,我使用了这个函数:

def _string_to_tuple(obj: Any) -> Any:
"""Convert serialized tuples back to original representation. Tuples need to have a leading "__tuple__" string.

Args:
    obj (Any): Typically a dict, tuple, list, int, or string.

Returns:
    Any: Input object with recovered tuples.
"""
# deep copy object to avoid manipulation during iteration
obj_copy = copy.deepcopy(obj)
# if the object is a dictionary
if isinstance(obj, dict):
    # iterate over every key
    for key in obj:
        # set for later to avoid modification in later iterations when this var does not get overwritten
        serialized_key = None
        # if key is a serialized tuple starting with the "__tuple__" affix
        if isinstance(key, str) and key.startswith("__tuple__"):
            # decode it so tuple
            serialized_key = tuple(key.split("__tuple__")[1].strip("[]").replace("'", "").split(", "))
            # if key is number in string representation
            if all(entry.isdigit() for entry in serialized_key):
                # convert to integer
                serialized_key = tuple(map(int, serialized_key))
            # replace old key with encoded key
            obj_copy[serialized_key] = obj_copy.pop(key)
        # if the key was modified
        if serialized_key is not None:
            # do it again for the next nested dictionary
            obj_copy[serialized_key] = _string_to_tuple(obj[key])
        # else, just do it for the next dictionary
        else:
            obj_copy[key] = _string_to_tuple(obj[key])
# if another instance was found
elif isinstance(obj, list):
    for item in obj:
        _string_to_tuple(item)
return obj_copy

插入用于对示例进行编码/解码的自定义逻辑,方法是更改

if isinstance(key, tuple):
    # stringify the key
    serialized_key = f"__tuple__{list(key)}"

分别在_tuple_to_string函数或来自_string_to_tuple函数的对应代码块中:

if isinstance(key, str) and key.startswith("__tuple__"):
    # decode it so tuple
    serialized_key = tuple(key.split("__tuple__")[1].strip("[]").replace("'", "").split(", "))
    # if key is number in string representation
    if all(entry.isdigit() for entry in serialized_key):
        # convert to integer
        serialized_key = tuple(map(int, serialized_key))

然后,您可以像往常一样使用它:

>>> dct = {("L1", "L1"): {("L2", "L2"): "foo"}}
>>> json.dumps(_tuple_to_string(dct))
... {"__tuple__['L1', 'L2']": {"__tuple__['L2', 'L2']": "foo"}}

希望我能帮你!

相关问题