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

Bru*_*tus 272 python unicode serialization json python-2.x

我正在使用Python 2ASCII编码的文本文件中解析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`
Run Code Online (Sandbox Code Playgroud)

更新

很久以前,当我遇到Python 2时,问这个问题.今天一个简单而干净的解决方案是使用最新版本的Python - 即Python 3和转发版.

Bru*_*tus 178

虽然这里有一些很好的答案,但我最终使用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']
Run Code Online (Sandbox Code Playgroud)

笔记

有些事情需要注意:

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

  • 你应该(可能总是)使用PyYAML的safe_load功能; 如果你用它来加载JSON文件,你无论如何都不需要该load功能的"附加功能" .

  • 如果你想要一个YAML解析器,它对 1.2版本的规范有更多的支持(并且正确解析非常低的数字),请尝试Ruamel YAML:pip install ruamel.yaml并且import ruamel.yaml as yaml是我在测试中所需要的.

转变

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

我现在使用Mark Amery的几次,效果很好而且非常容易使用.您也可以使用类似的功能object_hook,因为它可能会提高大文件的性能.请参阅Mirec Miskuf稍微提及的答案.

  • 如果您决定使用此答案,请稍加注意.它完全适用于Brutus的情况,但仅仅因为他知道他的数据只包含ASCII编码的字符.如果您没有这种保证,这个答案将无效.例如,尝试在Python shell中执行`yaml.load(json.dumps([u'a',u'£',u'É']))`并观察你回到'['a',u '\ xa3',u'\ xc9']`(包含`unicode`字符串).如果您不能确定您的数据只包含ASCII字符集中的字符,那么您应该使用不同的方法(我建议我自己的答案). (8认同)

Mar*_*ery 141

没有内置选项使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
Run Code Online (Sandbox Code Playgroud)

只需在您通过json.loadjson.loads拨打的输出上调用此选项即可.

几个笔记:

  • 要支持Python 2.6或更早版本,请替换return {byteify(key): byteify(value) for key, value in input.iteritems()}return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]),因为在Python 2.7之前不支持字典理解.
  • 由于此答案通过整个解码对象进行递归,因此它具有一些不期望的性能特征,可以通过非常小心地使用object_hookobject_pairs_hook参数来避免这些特性.到目前为止,Mirec Miskuf的答案是唯一能够正确解决这个问题的答案,尽管如此,它比我的方法复杂得多.


小智 100

一个解决方案 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
Run Code Online (Sandbox Code Playgroud)

用法示例:

>>> 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'}
Run Code Online (Sandbox Code Playgroud)

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

Mark Amery的功能比这些功能更短更清晰,那么它们的重点是什么?你为什么要用它们?

纯粹是为了表现.Mark的回答首先使用unicode字符串完全解码JSON文本,然后通过整个解码值进行递归,将所有字符串转换为字节字符串.这有几个不良影响:

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

这个答案通过缓解这两方面的性能问题object_hook的参数json.loadjson.loads.来自文档:

object_hook是一个可选函数,将使用任何对象文字解码的结果调用(a dict).将使用object_hook的返回值而不是dict.此功能可用于实现自定义解码器

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

Mark的答案不适合object_hook现场使用,因为它会递归到嵌套的词典中.我们阻止这个答案与该递归ignore_dicts参数_byteify,它被传递给它在任何时候都只是object_hook它传递一个新dict来byteify.该ignore_dicts标志告诉_byteify忽略dicts,因为它们已被字节化.

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

  • 这是很好的解决方案; 高效而优雅.但是,如果你被困在Python <2.7领域,就像我一样,你将需要替换行:`return {byteify(key,ignore_dicts = True):_byteify(value,ignore_dicts = True)for key, data.iteritems()}中的值与`return dict((_ byteify(key,ignore_dicts = True),_ byteify(value,ignore_dicts = True))的关键,data.iteritems()中的值``它的工作原理. (2认同)

Mik*_*nan 74

您可以使用object_hook参数json.loads来传入转换器.事后你不必进行转换.该json模块将始终object_hook仅传递dicts,并且它将递归传递嵌套的dicts,因此您不必自己递归到嵌套的dicts.我不认为我会将unicode字符串转换为Wells节目等数字.如果它是一个unicode字符串,它在JSON文件中被引用为一个字符串,所以它应该是一个字符串(或文件是坏的).

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

所以,例如:

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)
Run Code Online (Sandbox Code Playgroud)

  • 如果`s`中的对象是一个JSON`Object`(一个无序的key:value对,其中':'字符分隔键和值,逗号分隔并用花括号括起来),这很好.如果它是,例如,JSON"数组".因此,如果给出一个JSON"Array",如`["a","b"]`,结果仍然是`[u'a',u'b']`.对于`json.loads()`,其他当前可用的自定义钩子类型参数都不能完成这项任务. (3认同)
  • 因为,正如你所提到的,`json`模块将递归传递嵌套的`dict`,所以不必在两个函数中检查它们 - 所以应该删除检查它们的两个`elif`子句. (2认同)

nos*_*klo 37

那是因为json在字符串对象和unicode对象之间没有区别.它们都是javascript中的所有字符串.

我认为JSON是正确的返回unicode对象.事实上,我不会接受任何更少,因为javascript字符串实际上是unicode对象(即JSON(javascript)字符串可以存储任何类型的unicode字符)因此unicode在从JSON转换字符串时创建对象是有意义的.简单的字符串是不适合的,因为库必须猜测你想要的编码.

最好在unicode各处使用字符串对象.因此,您最好的选择是更新库,以便它们可以处理unicode对象.

但是如果你真的想要字节串,只需将结果编码为你选择的编码:

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']
Run Code Online (Sandbox Code Playgroud)


Cha*_*iam 14

有一种简单的解决方法.

TL; DR - 使用ast.literal_eval()而不是json.loads().双方astjson在标准库.

虽然不是一个"完美"的答案,但如果您的计划完全忽略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))
Run Code Online (Sandbox Code Playgroud)

得到:

JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}
Run Code Online (Sandbox Code Playgroud)

当某些对象确实是Unicode字符串时,这会变得更加毛茸茸.完整的答案很快变得毛茸茸.

  • 更好的是确保你的json不包含任何`null`,`true`或`false`值,因为它们在python中无效并且会导致`literal_eval()`失败. (11认同)
  • @ʇsәɹoɈ还希望你的JSON不包含字符串中的转义solidus(`\ /`),或者unicode转义序列(比如``\ u0061"`,这是写"a"的另一种方式``) .Python的文字语法在几个方面与JSON不兼容,我不相信任何我不会丢弃的脚本的答案. (3认同)

Tra*_*sen 11

Mike Brennan的答案很接近,但没有理由重新遍历整个结构.如果您使用object_hook_pairs(Python 2.7+)参数:

object_pairs_hook是一个可选函数,将使用对有序列表对解码的任何对象文字的结果进行调用.将使用返回值object_pairs_hook而不是dict.此功能可用于实现依赖于键和值对被解码的顺序的自定义解码器(例如,collections.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'}}
Run Code Online (Sandbox Code Playgroud)

请注意,我永远不必递归地调用钩子,因为当你使用时,每个对象都会被传递给钩子object_pairs_hook.您必须关心列表,但正如您所看到的,列表中的对象将被正确转换,您无需递归即可实现.

编辑:一位同事指出Python2.6没有object_hook_pairs.您仍然可以通过进行非常小的更改来使用Python2.6.在上面的钩子中,改变:

for key, value in pairs:
Run Code Online (Sandbox Code Playgroud)

for key, value in pairs.iteritems():
Run Code Online (Sandbox Code Playgroud)

然后使用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'}}
Run Code Online (Sandbox Code Playgroud)

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


Jar*_*die 9

我担心在simplejson库中无法自动实现这一点.

simplejson中的扫描仪和解码器旨在生成unicode文本.为此,库使用一个名为的函数c_scanstring(如果它可用,速度),或者py_scanstringC版本不可用.scanstring几乎所有simplejson用于解码可能包含文本的结构的例程都会多次调用该函数.你必须scanstring在simplejson.decoder或子类中使用monkeypatch 值,JSONDecoder并提供几乎所有可能包含文本的实现.

然而,simplejson输出unicode的原因是json规范特别提到"字符串是零个或多个Unicode字符的集合"......对unicode的支持被假定为格式本身的一部分.Simplejson的scanstring实现甚至扫描和解释unicode转义(甚至错误检查格式错误的多字节字符集表示),因此它可以可靠地将值返回给你的唯一方法是unicode.

如果您有一个需要的老化库str,我建议您在解析后仔细搜索嵌套数据结构(我承认您明确表示要避免...抱歉),或者可能将您的库包装在某种类型的库中在门面,您可以在更精细的水平上按下输入参数.如果您的数据结构确实是嵌套的,那么第二种方法可能比第一种方法更易于管理.