我使用Python 2从ASCII编码的文本文件解析JSON。

当用json或simplejson加载这些文件时,我的所有字符串值都转换为Unicode对象而不是字符串对象。问题是,我必须将数据与一些只接受字符串对象的库一起使用。我不能更改库也不能更新它们。

是否有可能获得字符串对象而不是Unicode对象?

例子

>>> import json
>>> original_list = ['a', 'b']
>>> json_list = json.dumps(original_list)
>>> json_list
'["a", "b"]'
>>> new_list = json.loads(json_list)
>>> new_list
[u'a', u'b']  # I want these to be of type `str`, not `unicode`

(2017年一个简单而干净的解决方案是使用最新版本的Python——即Python 3和更高版本。)


当前回答

只需使用pickle而不是json来转储和加载,如下所示:

    import json
    import pickle

    d = { 'field1': 'value1', 'field2': 2, }

    json.dump(d,open("testjson.txt","w"))

    print json.load(open("testjson.txt","r"))

    pickle.dump(d,open("testpickle.txt","w"))

    print pickle.load(open("testpickle.txt","r"))

它产生的输出是(字符串和整数被正确处理):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}

其他回答

没有内置选项让json模块函数返回字节字符串而不是Unicode字符串。然而,这个简短而简单的递归函数将任何解码的JSON对象从使用Unicode字符串转换为utf -8编码的字节字符串:

def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value)
                for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input

只需在从json中获得的输出上调用此函数。加载或json。负载的电话。

几点注意事项:

To support Python 2.6 or earlier, replace return {byteify(key): byteify(value) for key, value in input.iteritems()} with return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), since dictionary comprehensions weren't supported until Python 2.7. Since this answer recurses through the entire decoded object, it has a couple of undesirable performance characteristics that can be avoided with very careful use of the object_hook or object_pairs_hook parameters. Mirec Miskuf's answer is so far the only one that manages to pull this off correctly, although as a consequence, it's significantly more complicated than my approach.

虽然这里有一些很好的答案,但我最终使用PyYAML来解析我的JSON文件,因为它以str类型字符串而不是unicode类型给出键和值。因为JSON是YAML的一个子集,它工作得很好:

>>> import json
>>> import yaml
>>> list_org = ['a', 'b']
>>> list_dump = json.dumps(list_org)
>>> list_dump
'["a", "b"]'
>>> json.loads(list_dump)
[u'a', u'b']
>>> yaml.safe_load(list_dump)
['a', 'b']

笔记

但有一些事情需要注意:

I get string objects because all my entries are ASCII encoded. If I would use Unicode encoded entries, I would get them back as unicode objects — there is no conversion! You should (probably always) use PyYAML's safe_load function; if you use it to load JSON files, you don't need the "additional power" of the load function anyway. If you want a YAML parser that has more support for the 1.2 version of the spec (and correctly parses very low numbers) try Ruamel YAML: pip install ruamel.yaml and import ruamel.yaml as yaml was all I needed in my tests.

转换

如上所述,没有任何转换!如果你不能确定只处理ASCII值(而且大多数时候你不能确定),最好使用转换函数:

我现在用过几次Mark Amery的,效果很好,很容易使用。您还可以使用类似的函数作为object_hook,因为它可以提高大文件的性能。请参阅Mirec Miskuf稍复杂的回答。

你可以为json使用object_hook参数。要传入转换器的负载。你不需要在事后进行转换。json模块将始终只传递object_hook字典,并且它将递归地传递嵌套字典,因此您不必自己递归到嵌套字典。我不认为我会像Wells显示的那样将Unicode字符串转换为数字。如果它是Unicode字符串,它在JSON文件中被引用为字符串,所以它应该是字符串(或者文件是坏的)。

另外,我会尽量避免在unicode对象上做类似str(val)的事情。您应该使用带有有效编码的value.encode(encoding),这取决于外部库的期望。

举个例子:

def _decode_list(data):
    rv = []
    for item in data:
        if isinstance(item, unicode):
            item = item.encode('utf-8')
        elif isinstance(item, list):
            item = _decode_list(item)
        elif isinstance(item, dict):
            item = _decode_dict(item)
        rv.append(item)
    return rv

def _decode_dict(data):
    rv = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        elif isinstance(value, list):
            value = _decode_list(value)
        elif isinstance(value, dict):
            value = _decode_dict(value)
        rv[key] = value
    return rv

obj = json.loads(s, object_hook=_decode_dict)

使用object_hook的解决方案

它适用于Python 2.7和3.x。

import json

def json_load_byteified(file_handle):
    return _byteify(
        json.load(file_handle, object_hook=_byteify),
        ignore_dicts=True
    )

def json_loads_byteified(json_text):
    return _byteify(
        json.loads(json_text, object_hook=_byteify),
        ignore_dicts=True
    )

def _byteify(data, ignore_dicts = False):
    if isinstance(data, str):
        return data

    # If this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item, ignore_dicts=True) for item in data ]
    # If this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict) and not ignore_dicts:
        return {
            _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
            for key, value in data.items() # changed to .items() for Python 2.7/3
        }

    # Python 3 compatible duck-typing
    # If this is a Unicode string, return its string representation
    if str(type(data)) == "<type 'unicode'>":
        return data.encode('utf-8')

    # If it's anything else, return it in its original form
    return data

使用示例:

>>> json_loads_byteified('{"Hello": "World"}')
{'Hello': 'World'}
>>> json_loads_byteified('"I am a top-level string"')
'I am a top-level string'
>>> json_loads_byteified('7')
7
>>> json_loads_byteified('["I am inside a list"]')
['I am inside a list']
>>> json_loads_byteified('[[[[[[[["I am inside a big nest of lists"]]]]]]]]')
[[[[[[[['I am inside a big nest of lists']]]]]]]]
>>> json_loads_byteified('{"foo": "bar", "things": [7, {"qux": "baz", "moo": {"cow": ["milk"]}}]}')
{'things': [7, {'qux': 'baz', 'moo': {'cow': ['milk']}}], 'foo': 'bar'}
>>> json_load_byteified(open('somefile.json'))
{'more json': 'from a file'}

它是如何工作的,我为什么要使用它?

Mark Amery的函数比这些更短更清楚,那么它们的意义是什么呢?你为什么要用它们?

纯粹是为了表现。Mark的回答首先用Unicode字符串完整地解码JSON文本,然后递归地遍历整个解码后的值,将所有字符串转换为字节字符串。这有一些不好的影响:

在内存中创建整个解码结构的副本 如果您的JSON对象嵌套非常深(500级或更多),那么您将达到Python的最大递归深度

这个答案通过使用json的object_hook参数缓解了这两个性能问题。Load和json.loads。从文档中可以看到:

Object_hook是一个可选函数,它将在任何对象文字解码(dict)的结果中被调用。将使用object_hook的返回值而不是dict。此特性可用于实现自定义解码器

由于在其他字典中嵌套了许多层的字典在解码时被传递给object_hook,因此我们可以在此时对其中的任何字符串或列表进行字节化,从而避免以后需要进行深度递归。

Mark的答案不适合作为object_hook使用,因为它递归到嵌套字典中。我们通过ignore_dicts形参到_byteify来防止这个答案中的递归,除了object_hook向它传递一个新的dict给byteify时,这个参数一直被传递给它。ignore_dicts标志告诉_byteify忽略字典,因为字典已经被字节化了。

最后,我们实现的json_load_byteify和json_loads_byteify对json返回的结果调用_byteify(带ignore_dicts=True)。加载或json。加载来处理被解码的JSON文本在顶层没有字典的情况。

Mark (Amery)正确地指出:在JSON转储上使用PyYAML的反序列化器仅在只有ASCII时有效。至少是开箱即用。

关于PyYAML方法的两个简短评论:

永远不要对来自字段的数据使用yaml.load()。这是YAML的一个特性(!),可以执行隐藏在结构中的任意代码。 你也可以通过以下方法使它适用于非ASCII: Def to_utf8(加载器,节点): 返回loader.construct_scalar(节点).encode(“utf - 8”) yaml.add_constructor (u 'tag: yaml.org, 2002: str ', to_utf8)

但就性能而言,这与马克·艾默里的答案无法相提并论:

将一些深度嵌套的样本字典扔到这两个方法上,我得到了这个(与dt[j] = json.loads(json.dumps(m))的时间delta):

     dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j]
     dt[byteify recursion(Mark Amery)] =~   5 * dt[j]

因此,反序列化(包括完全遍历树和编码)完全在基于c语言的JSON实现的数量级之内。我发现这非常快,而且在深度嵌套结构上比yaml加载更健壮。更少的安全错误,看yaml.load。

虽然我很喜欢一个指向c语言的转换器的指针,但byteify函数应该是默认答案。

如果JSON结构来自包含用户输入的字段,则尤其如此。因为这样你可能需要遍历你的结构——独立于你想要的内部数据结构(“unicode三明治”或字节字符串)。

Why?

Unicode正常化。给不知情的人:吃片止痛药,看看这篇文章。

所以使用byteify递归你一石二鸟:

从嵌套的JSON转储中获取字节串 让用户输入值正常化,这样你就可以在你的存储中找到东西。

在我的测试中,结果是将input.encode('utf-8')替换为unicodedata。normalize('NFC', input).encode('utf-8')甚至比没有NFC时还要快——但我猜这在很大程度上依赖于样本数据。