如何从JSON获取字符串对象而不是Unicode?

2024-04-28 02:07:35 发布

您现在位置:Python中文网/ 问答频道 /正文

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

当使用^{}^{}加载这些文件时,我的所有字符串值都转换为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`

更新

这个问题很久以前就被问到了,当时我正忙于使用Python 2。今天一个简单而干净的解决方案是使用最新版本的Python,即Python 3并转发。


Tags: 文件数据对象字符串importjson示例编码
3条回答

虽然这里有一些好的答案,但我最终使用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']

注释

不过,需要注意的是:

  • 我得到字符串对象,因为我的所有条目都是用ASCII编码的。如果我使用unicode编码的条目,我会将它们作为unicode对象返回-没有转换!

  • 您应该(可能总是)使用PyYAML的safe_load函数;如果您使用它来加载JSON文件,那么无论如何,您不需要load函数的“额外功能”。

  • 如果您想要一个对规范的1.2版本有更多支持的YAML解析器(和correctly parses very low numbers),请尝试Ruamel YAMLpip install ruamel.yaml并且import ruamel.yaml as yaml是我在测试中所需要的全部。

转换

如前所述,没有转换!如果不能确定只处理ASCII值(而且大多数情况下不能确定),最好使用转换函数:

我已经用过Mark Amery中的一个了好几次,它工作得很好,而且很容易使用。您还可以使用类似于object_hook的函数,因为它可能会提高大文件的性能。请参阅稍有涉及的answer from Mirec Miskuf

具有object_hook

的解
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 this is a unicode string, return its string representation
    if isinstance(data, unicode):
        return data.encode('utf-8')
    # 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.iteritems()
        }
    # 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's function比这些短而清晰,那么它们有什么意义呢?你为什么要用它们?

纯粹用于性能。Mark的答案首先使用unicode字符串对JSON文本进行完全解码,然后通过整个解码值递归,将所有字符串转换为字节字符串。这有两个不良影响:

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

这个答案通过使用json.loadjson.loadsobject_hook参数缓解了这两个性能问题。来自the docs

object_hook is an optional function that will be called with the result of any object literal decoded (a dict). The return value of object_hook will be used instead of the dict. This feature can be used to implement custom decoders

由于词典在其他词典中嵌套了许多深层次,当它们被解码时会传递给object_hook,因此我们可以在此时指定其中的任何字符串或列表,避免以后需要深层递归。

Mark的答案不适合作为object_hook使用,因为它递归到嵌套字典中。我们用_byteifyignore_dicts参数防止这个答案中的递归,除了object_hook向byteify传递一个新的dict时的之外,这个参数始终传递给它。ignore_dicts标志告诉_byteify忽略dict,因为它们已经被指定了。

最后,我们的json_load_byteifiedjson_loads_byteified实现对json.loadjson.loads返回的结果调用_byteify(使用ignore_dicts=True),以处理正在解码的JSON文本在顶层没有dict的情况。

没有使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.loadjson.loads调用获得的输出上调用此函数。

几条注释:

  • 要支持Python2.6或更早版本,请将return {byteify(key): byteify(value) for key, value in input.iteritems()}替换为return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]),因为直到Python2.7才支持字典理解。
  • 由于此答案在整个解码对象中递归,因此它有两个不需要的性能特征,可以通过非常小心地使用object_hookobject_pairs_hook参数来避免。Mirec Miskuf's answer是目前为止唯一一个能够正确实现这一点的方法,尽管结果是,它比我的方法要复杂得多。

相关问题 更多 >