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


当前回答

虽然这里有一些很好的答案,但我最终使用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稍复杂的回答。

其他回答

迈克·布伦南的答案很接近,但没有任何理由重新审视整个结构。如果使用object_hook_pairs (Python 2.7+)形参:

Object_pairs_hook是一个可选函数,它将使用任意对象字面量的解码结果调用。object_pairs_hook的返回值将被使用,而不是字典。此特性可用于实现依赖于键和值对解码顺序的自定义解码器(例如集合)。OrderedDict将记住插入的顺序)。如果还定义了object_hook,则object_pairs_hook具有优先级。

有了它,你可以得到每个JSON对象,所以你可以不需要递归地进行解码:

def deunicodify_hook(pairs):
    new_pairs = []
    for key, value in pairs:
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        new_pairs.append((key, value))
    return dict(new_pairs)

In [52]: open('test.json').read()
Out[52]: '{"1": "hello", "abc": [1, 2, 3], "def": {"hi": "mom"}, "boo": [1, "hi", "moo", {"5": "some"}]}'

In [53]: json.load(open('test.json'))
Out[53]:
{u'1': u'hello',
 u'abc': [1, 2, 3],
 u'boo': [1, u'hi', u'moo', {u'5': u'some'}],
 u'def': {u'hi': u'mom'}}

In [54]: json.load(open('test.json'), object_pairs_hook=deunicodify_hook)
Out[54]:
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

注意,我从来没有递归地调用钩子,因为当你使用object_pairs_hook时,每个对象都会被传递给钩子。您确实需要关心列表,但是正如您所看到的,列表中的对象将被正确地转换,并且您不必递归来实现它。

一位同事指出Python2.6没有object_hook_pairs。你仍然可以通过做一个很小的改变来使用这个will Python2.6。在上面的钩子中,更改:

for key, value in pairs:

to

for key, value in pairs.iteritems():

然后使用object_hook代替object_pairs_hook:

In [66]: json.load(open('test.json'), object_hook=deunicodify_hook)
Out[66]:
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

使用object_pairs_hook可以为JSON对象中的每个对象少实例化一个字典,如果您正在解析一个巨大的文档,那么这样做可能是值得的。

问题在于simplejson和json是两个不同的模块,至少在处理Unicode的方式上是这样。你在Python 2.6+中有json,它给你Unicode值,而simplejson返回字符串对象。

在您的环境中尝试easy_installing -ing simplejson,看看是否有效。对我来说确实如此。

恐怕在simplejson库中没有任何方法可以自动实现这一点。

The scanner and decoder in simplejson are designed to produce Unicode text. To do this, the library uses a function called c_scanstring (if it's available, for speed), or py_scanstring if the C version is not available. The scanstring function is called several times by nearly every routine that simplejson has for decoding a structure that might contain text. You'd have to either monkey patch the scanstring value in simplejson.decoder, or subclass JSONDecoder and provide pretty much your own entire implementation of anything that might contain text.

然而,simplejson输出Unicode的原因是JSON规范特别提到“字符串是0个或多个Unicode字符的集合”……对Unicode的支持被假定为格式本身的一部分。simplejson的扫描字符串实现甚至扫描和解释Inicode转义(甚至错误检查格式不正确的多字节字符集表示),因此它能够可靠地将值返回给您的唯一方法是Unicode。

如果你有一个老旧的库,需要一个str,我建议你在解析后费力地搜索嵌套的数据结构(我承认这是你明确说过你想避免的…对不起),或者可能将库包装在某种外观中,在这种外观中您可以在更细粒度的级别上处理输入参数。如果数据结构确实嵌套很深,第二种方法可能比第一种方法更易于管理。

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

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)

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

我也遇到了同样的问题。

因为我需要将所有数据传递给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