我使用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和更高版本。)


当前回答

使用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文本在顶层没有字典的情况。

其他回答

我有一个JSON字典作为字符串。键和值是Unicode对象,如下例所示:

myStringDict = "{u'key':u'value'}"

我可以使用上面建议的byteify函数,使用ast.literal_eval(myStringDict)将字符串转换为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中获得的输出上调用此函数。加载或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.

我也遇到了同样的问题。

因为我需要将所有数据传递给PyGTK,所以Unicode字符串对我来说也不是很有用。这是另一种递归转换方法。实际上,类型安全的JSON转换也需要它——JSON .dump()会放弃任何非字面量,比如Python对象。但是它不转换字典索引。

# removes any objects, turns Unicode back into str
def filter_data(obj):
        if type(obj) in (int, float, str, bool):
                return obj
        elif type(obj) == unicode:
                return str(obj)
        elif type(obj) in (list, tuple, set):
                obj = list(obj)
                for i,v in enumerate(obj):
                        obj[i] = filter_data(v)
        elif type(obj) == dict:
                for i,v in obj.iteritems():
                        obj[i] = filter_data(v)
        else:
                print "invalid object in data, converting to string"
                obj = str(obj)
        return obj

使用钩子支持Python 2和3(来自Mirec Miskuf的回答):

import requests
import six
from six import iteritems

requests.packages.urllib3.disable_warnings()  # @UndefinedVariable
r = requests.get("http://echo.jsontest.com/key/value/one/two/three", verify=False)

def _byteify(data):
    # If this is a Unicode string, return its string representation
    if isinstance(data, six.string_types):
        return str(data.encode('utf-8').decode())

    # If this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item) 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):
        return {
            _byteify(key): _byteify(value) for key, value in iteritems(data)
        }
    # If it's anything else, return it in its original form
    return data

w = r.json(object_hook=_byteify)
print(w)

返回:

 {'three': '', 'key': 'value', 'one': 'two'}

有一个简单的变通办法。

DR -使用ast.literal_eval()代替json.loads()。ast和json都在标准库中。

虽然这不是一个“完美”的答案,但如果您的计划是完全忽略Unicode,那么它就相当不错了。Python 2.7

import json, ast
d = { 'field' : 'value' }
print "JSON Fail: ", json.loads(json.dumps(d))
print "AST Win:", ast.literal_eval(json.dumps(d))

给:

JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}

当一些对象实际上是Unicode字符串时,这就变得更麻烦了。完整的答案很快就变得棘手起来。