How to get string objects instead of Unicode from JSON?
我使用python 2从ASCII编码的文本文件解析JSON。
当用
是否可以获取字符串对象而不是Unicode对象?
例子1 2 3 4 5 6 7 8 | >>> 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和forward。
虽然这里有一些很好的答案,但我最终还是使用pyyaml来解析我的JSON文件,因为它给出的键和值是
1 2 3 4 5 6 7 8 9 10 | >>> 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解析器(并且正确地解析非常低的数字),请尝试ruamel yaml:
pip install ruamel.yaml 和import ruamel.yaml as yaml 是我在测试中所需要的。
转换
如前所述,没有转换!如果您不能确定只处理ASCII值(并且大部分时间都不能确定),最好使用转换函数:
我用过几次马克·阿米里的那个,效果很好,而且很容易使用。您也可以使用类似于
没有使JSON模块函数返回字节字符串而不是Unicode字符串的内置选项。但是,这个简短而简单的递归函数将把任何解码后的JSON对象从使用Unicode字符串转换为UTF-8编码的字节字符串:
1 2 3 4 5 6 7 8 9 10 | 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 |
只需在您从
几个注意事项:
- 为了支持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_hook 或object_pairs_hook 参数来避免。Mirec Miskuf的答案是迄今为止唯一能够正确实现这一点的答案,尽管其结果比我的方法要复杂得多。
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 |
示例用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> 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的答案首先使用Unicode字符串完全解码JSON文本,然后通过整个解码值进行循环,以将所有字符串转换为字节字符串。这有一些不良影响:
- 在内存中创建整个解码结构的副本
- 如果您的JSON对象确实是深度嵌套的(500层或更多),那么您将达到Python的最大递归深度。
这个答案通过使用
object_hook is an optional function that will be called with the result of any object literal decoded (adict ). The return value of object_hook will be used instead of thedict . This feature can be used to implement custom decoders
由于词典在其他词典中嵌套了许多层次,因此在对它们进行解码时,可以将它们传递给
Mark的答案不适合用作现有的
最后,我们对
可以使用
另外,我会尽量避免在一个
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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) |
这是因为JSON在字符串对象和Unicode对象之间没有区别。它们都是javascript中的字符串。
我认为JSON返回Unicode对象是正确的。事实上,我不会接受更少的内容,因为javascript字符串实际上是
最好到处使用
但是,如果您真的想要字节串,只需将结果编码为您选择的编码:
1 2 3 4 5 6 | >>> nl = json.loads(js) >>> nl [u'a', u'b'] >>> nl = [s.encode('utf-8') for s in nl] >>> nl ['a', 'b'] |
这里有一个简单的工作。
tl;dr-使用
虽然不是一个"完美"的答案,但如果您的计划是完全忽略Unicode,它会得到一个相当远的答案。在Python 2.7中
1 2 3 4 | import json, ast d = { 'field' : 'value' } print"JSON Fail:", json.loads(json.dumps(d)) print"AST Win:", ast.literal_eval(json.dumps(d)) |
给予:
1 2 | JSON Fail: {u'field': u'value'} AST Win: {'field': 'value'} |
当某些对象实际上是Unicode字符串时,这会变得更加复杂。完整的答案很快就会变得复杂起来。
迈克·布伦南的答案很接近,但没有理由重新穿越整个结构。如果使用
object_pairs_hook is an optional function that will be called with the result of any object literal decoded with an ordered list of pairs. The return value ofobject_pairs_hook will be used instead of thedict . This feature can be used to implement custom decoders that rely on the order that the key and value pairs are decoded (for example,collections.OrderedDict will remember the order of insertion). Ifobject_hook is also defined, theobject_pairs_hook takes priority.
有了它,您就可以将每个JSON对象交给您,这样您就可以在不需要递归的情况下进行解码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 <div class="suo-content">[collapse title=""]<ul><li>这很整洁,似乎非常接近于应该得到绿色的对勾(令人钦佩的是,当更好的答案出现时,布鲁图斯已经慷慨地传递了这一点)。但是…为什么不在这个答案中显示的<wyn>deunicodify_hook</wyn>中正确处理列表呢?目前,您有一个<wyn>deunicodify_hook</wyn>的实现,它不迭代列表,也不分解其中的字符串和列表,因此显示的输出与钩子实际产生的输出不匹配。解决这个问题,这个答案会比我的好。</li><li>轻浮:我还建议用普通的cpython解释器来演示这个函数,而不是你在这里使用的那个解释器(我认为它是Ironpython)?cpython解释器对大多数Python用户来说更熟悉,在我看来,它更漂亮。</li><li>这对我不起作用,但我相信这是我所做的一些奇怪的事情…我正在将一个较大的JSON文档中的一个列表存储到一个文件中。无论我加载它时使用或不使用这个对象对挂钩,每个项目都会使用Unicode。织补。</li><li>@很有意义!由于只对对象调用<wyn>object_pairs_hook</wyn>,如果JSON文本在顶层有一个字符串列表,那么这个解决方案将失败。如果不对从<wyn>json.load</wyn>返回的对象调用某些函数,就无法修复此问题;<wyn>json.load</wyn>钩子都不能保证您能够处理每个字符串。我认为这是一个足够大的缺陷,我可以继续推荐我的解决方案,而不是使用钩子。</li><li>-1因为我刚刚意识到Mirec Miskuf已经发布了一个对象钩子答案,它既没有Mike Brennan方法的缺点(多次重复相同的字典),也没有这个方法的缺点(未能通过嵌套列表或顶级列表或字符串)。我不知道为什么他的回答几乎没有引起注意,而这一次-这是次-很快获得了选票。</li></ul>[/collapse]</div><hr><P>恐怕在simplejson库中无法自动实现这一点。</P><P>simplejson中的扫描仪和解码器设计用于生成Unicode文本。为此,库使用一个名为<wyn>c_scanstring</wyn>的函数(如果它可用,则用于speed),如果C版本不可用,则使用<wyn>py_scanstring</wyn>的函数。simplejson对可能包含文本的结构进行解码时,几乎每个例程都会多次调用<wyn>scanstring</wyn>函数。您要么在simplejson.decoder中对<wyn>scanstring</wyn>值进行monkeypatch,要么在<wyn>JSONDecoder</wyn>子类中提供几乎所有可能包含文本的实现。</P><P>然而,simplejson输出unicode的原因是json规范特别提到了"字符串是零个或多个unicode字符的集合"…假定对Unicode的支持是格式本身的一部分。simplejson的<wyn>scanstring</wyn>实现可以扫描和解释unicode转义(甚至错误检查多字节字符集表示形式的错误),因此它能够可靠地将值返回给您的唯一方法是unicode。</P><P>如果您有一个旧的库需要一个<wyn>str</wyn>,我建议您要么在解析后费力地搜索嵌套的数据结构(我承认这是您明确表示要避免的事情)。抱歉),或者将库包装在某种外观中,您可以在更细粒度的级别上按摩输入参数。如果数据结构确实是深度嵌套的,那么第二种方法可能比第一种方法更易于管理。</P><hr><P>正如Mark(Amery)正确指出的那样:在JSON转储上使用pyyaml的反序列化程序只在您只有ASCII的情况下才有效。至少是开箱即用。</P><P>关于Pyyaml方法的两个快速评论:</P><li><P>切勿对来自现场的数据使用yaml.load。它是一个功能!!)执行隐藏在结构中的任意代码。</P></li><li><P>您也可以通过以下方式使其适用于非ASCII:</P>[cc lang="python"]def to_utf8(loader, node): return loader.construct_scalar(node).encode('utf-8') yaml.add_constructor(u'tag:yaml.org,2002:str', to_utf8) |
但从性能上看,这与马克·阿米里的答案没有什么区别:
将一些深度嵌套的示例dict放到这两个方法中,我得到了这个结果(dt[j]=json.loads的时间增量(json.dumps(m)):
1 2 | dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j] dt[byteify recursion(Mark Amery)] =~ 5 * dt[j] |
因此,反序列化包括完全遍历树和编码,完全符合JSON基于C的实现的数量级。我发现这非常快,而且它也比深嵌套结构的yaml负载更坚固。并且不太容易出现安全错误,查看yaml.load。
=>虽然我希望有一个指向仅基于C的转换器的指针,但是byteify函数应该是默认答案。
如果JSON结构来自包含用户输入的字段,则这一点尤其适用。因为这样,您可能需要遍历您的结构——独立于所需的内部数据结构("unicode三明治"或字节字符串)。
为什么?
Unicode标准化。对于不知情者:服用止痛药并阅读本书。
所以使用byteify递归,你可以用一块石头杀死两只鸟:
在我的测试中,结果发现用unicodedata.normalize("nfc",input).encode("utf-8")替换input.encode("utf-8")比不使用nfc更快,但这很大程度上取决于样本数据。
gotcha是
只需使用pickle而不是json进行转储和加载,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 | import json import pickle d = { 'field1': 'value1', 'field2': 2, } json.dump(d,open("testjson.txt","w")) print json.load(open("testjson.txt","r")) pickle.dump(d,open("testpickle.txt","w")) print pickle.load(open("testpickle.txt","r")) |
它产生的输出是(字符串和整数处理正确):
1 2 | {u'field2': 2, u'field1': u'value1'} {'field2': 2, 'field1': 'value1'} |
所以,我也遇到了同样的问题。猜猜谷歌的第一个结果是什么。
因为我需要将所有数据传递给Pygtk,所以Unicode字符串对我也不是很有用。所以我有另一个递归转换方法。它实际上也是类型安全JSON转换所必需的——json.dump()可以在任何非文本(如python对象)上提供帮助。但不转换dict索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 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 |
使用钩子支持python2&3(来自https://stackoverflow.com/a/3357117/558397)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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) |
返回:
1 | {'three': '', 'key': 'value', 'one': 'two'} |
对于Python3.6,有时我仍然会遇到这个问题。例如,当从RESTAPI获取响应并将响应文本加载到JSON时,我仍然会得到Unicode字符串。使用json.dumps()找到了一个简单的解决方案。
1 2 | response_message = json.loads(json.dumps(response.text)) print(response_message) |
我有一个JSON听写作为字符串。键和值是Unicode对象,如下例所示:
1 | myStringDict ="{u'key':u'value'}" |
我可以使用上面建议的
下面是用C编写的递归编码器:https://github.com/axiros/nested_编码
与json.loads相比,"平均"结构的性能开销约为10%。
1 2 3 4 | 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% |
使用此测试结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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) |
Check out this answer to a similar question like this which states that
The u- prefix just means that you have a Unicode string. When you really use the string, it won't appear in your data. Don't be thrown by the printed output.
For example, try this:
1 | print mail_accounts[0]["i"] |
你不会看到美国。
这是游戏后期,但我建立了这个递归的施法者。它能满足我的需要,我认为它相对完整。这可能对你有帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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对象:
1 2 | obj = json.loads(content, parse_float=float, parse_int=int) obj = _parseJSON(obj) |
我将它作为类的私有成员,但您可以根据需要重新调整方法的用途。
我重写了Wells的_parse_json()来处理JSON对象本身是数组的情况(我的用例)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def _parseJSON(self, obj): if isinstance(obj, dict): newobj = {} for key, value in obj.iteritems(): key = str(key) newobj[key] = self._parseJSON(value) elif isinstance(obj, list): newobj = [] for value in obj: newobj.append(self._parseJSON(value)) elif isinstance(obj, unicode): newobj = str(obj) else: newobj = obj return newobj |
我修改了马克·阿米里的答案中的代码,特别是为了去掉
手动进行编码,禁用
If ensure_ascii is True (the default), all non-ASCII characters in the output are escaped with \uXXXX sequences
免责声明:在教科书中我使用匈牙利语。一些与匈牙利语相关的显著字符编码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | # 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规范,引用了:
A string is a collection of zero or more Unicode characters
在我的用例中,我有JSON文件。它们是
doctest并不是特别周到,但我共享代码,希望它对某些人有用。
我也遇到了这个问题,不得不处理JSON,我想出了一个小循环,将Unicode键转换为字符串。(GAE上的
1 2 3 4 5 6 | if NAME_CLASS_MAP.has_key(cls): kwargs = {} for i in obj.keys(): kwargs[str(i)] = obj[i] o = NAME_CLASS_MAP[cls](**kwargs) o.save() |
我把
不是像井的解决方案那样强大,而是小得多。