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


当前回答

你可以为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)

其他回答

我从Mark Amery的回答中改编了代码,特别是为了摆脱isinstance的鸭子键入的优点。

编码是手动完成的,ensure_ascii是禁用的。json的Python文档。Dump说:

如果ensure_ascii为True(默认值),输出中的所有非ascii字符将使用\uXXXX序列转义

免责声明:在文档测试中,我使用了匈牙利语。一些著名的与匈牙利语相关的字符编码有:cp852,在DOS中使用的IBM/OEM编码(有时被称为ASCII)。我认为这是不正确的,因为它取决于代码页设置)。Windows-1250用于Windows(有时称为ANSI,取决于区域设置),ISO 8859-1有时用于HTTP服务器。

测试文本Tüskéshátú kígyóbűvölő来自Koltai László(本地个人姓名形式),来自维基百科。

# coding: utf-8
"""
This file should be encoded correctly with utf-8.
"""
import json

def encode_items(input, encoding='utf-8'):
    u"""original from: https://stackoverflow.com/a/13101776/611007
    adapted by SO/u/611007 (20150623)
    >>>
    >>> ## run this with `python -m doctest <this file>.py` from command line
    >>>
    >>> txt = u"Tüskéshátú kígyóbűvölő"
    >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
    >>> txt3 = u"uúuutifu"
    >>> txt4 = b'u\\xfauutifu'
    >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
    >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
    >>> txt4u = txt4.decode('cp1250')
    >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
    >>> txt5 = b"u\\xc3\\xbauutifu"
    >>> txt5u = txt5.decode('utf-8')
    >>> txt6 = u"u\\u251c\\u2551uutifu"
    >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
    >>> assert txt == there_and_back_again(txt)
    >>> assert txt == there_and_back_again(txt2)
    >>> assert txt3 == there_and_back_again(txt3)
    >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
    >>> assert txt3 == txt4u,(txt3,txt4u)
    >>> assert txt3 == there_and_back_again(txt5)
    >>> assert txt3 == there_and_back_again(txt5u)
    >>> assert txt3 == there_and_back_again(txt4u)
    >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
    >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
    >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
    >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
    >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
    >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
    >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
    >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
    """
    try:
        input.iteritems
        return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
    except AttributeError:
        if isinstance(input, unicode):
            return input.encode(encoding)
        elif isinstance(input, str):
            return input
        try:
            iter(input)
            return [encode_items(e) for e in input]
        except TypeError:
            return input

def alt_dumps(obj, **kwargs):
    """
    >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
    '{"a": "T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
    """
    if 'ensure_ascii' in kwargs:
        del kwargs['ensure_ascii']
    return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

我还想强调Jarret Hardie引用JSON规范的答案,引用如下:

字符串是零个或多个Unicode字符的集合

在我的用例中,我有带有JSON内容的文件。它们是UTF-8编码的文件。ensure_ascii的结果是正确转义,但不是非常可读的JSON文件,这就是为什么我改编了Mark Amery的答案来满足我的需要。

doctest不是特别周到,但我分享了代码,希望它对某人有用。

下面是一个用C语言编写的递归编码器: https://github.com/axiros/nested_encode

与json.loads()相比,“平均”结构的性能开销约为10%。

python speed.py
  json loads            [0.16sec]: {u'a': [{u'b': [[1, 2, [u'\xd6ster..
  json loads + encoding [0.18sec]: {'a': [{'b': [[1, 2, ['\xc3\x96ster.
  time overhead in percent: 9%

使用这个测试结构:

import json, nested_encode, time

s = """
{
  "firstName": "Jos\\u0301",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "\\u00d6sterreich",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "children": [],
  "spouse": null,
  "a": [{"b": [[1, 2, ["\\u00d6sterreich"]]]}]
}
"""


t1 = time.time()
for i in xrange(10000):
    u = json.loads(s)
dt_json = time.time() - t1

t1 = time.time()
for i in xrange(10000):
    b = nested_encode.encode_nested(json.loads(s))
dt_json_enc = time.time() - t1

print "json loads            [%.2fsec]: %s..." % (dt_json, str(u)[:20])
print "json loads + encoding [%.2fsec]: %s..." % (dt_json_enc, str(b)[:20])

print "time overhead in percent: %i%%"  % (100 * (dt_json_enc - dt_json)/dt_json)

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

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

我可以使用上面建议的byteify函数,使用ast.literal_eval(myStringDict)将字符串转换为dict对象。

我构建了这个递归施法者。它符合我的需要,我认为它是相对完整的。

def _parseJSON(self, obj):
    newobj = {}

    for key, value in obj.iteritems():
        key = str(key)

        if isinstance(value, dict):
            newobj[key] = self._parseJSON(value)
        elif isinstance(value, list):
            if key not in newobj:
                newobj[key] = []
                for i in value:
                    newobj[key].append(self._parseJSON(i))
        elif isinstance(value, unicode):
            val = str(value)
            if val.isdigit():
                val = int(val)
            else:
                try:
                    val = float(val)
                except ValueError:
                    val = str(val)
            newobj[key] = val

    return newobj

只需要像这样传递一个JSON对象:

obj = json.loads(content, parse_float=float, parse_int=int)
obj = _parseJSON(obj)

我把它作为一个类的私有成员,但您可以根据需要重新使用该方法。

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