我正在使用 Python 2 来解析 ASCII encoded 文本文件中的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`
更新
很久以前,当我被困 Python 2 时,这个问题被问到了 . 今天一个简单而干净的解决方案是使用最新版本的Python - 即 Python 3 和转发 .
21 回答
我有一个JSON字典作为字符串 . 键和值是unicode对象,如下例所示:
我可以通过使用
ast.literal_eval(myStringDict)
将字符串转换为dict
对象来使用上面建议的byteify
函数 .查看this对类似问题的回答,其中说明了这一点
u-前缀只表示您有一个Unicode字符串 . 当您真正使用该字符串时,它不会出现在您的数据中 . 不要被打印输出抛出 .
例如,试试这个:
你不会看到你 .
使用hook支持Python2和3(来自https://stackoverflow.com/a/33571117/558397)
返回:
只需使用pickle代替json进行转储和加载,如下所示:
它产生的输出是(正确处理字符串和整数):
您可以使用json.loads的
object_hook
参数传入转换器 . 事后你不必进行转换 . json模块将始终仅传递object_hook
dicts,并且它将以递归方式传递嵌套的dicts,因此您不会认为我会将unicode字符串转换为Wells节目等数字 . 如果它是一个unicode字符串,它在JSON文件中被引用为一个字符串,所以它应该是一个字符串(或文件是坏的) .另外,我会尽量避免在
unicode
对象上做str(val)
之类的事情 . 您应该将value.encode(encoding)
与有效编码一起使用,具体取决于外部lib期望的内容 .所以,例如:
使用object_hook的解决方案
用法示例:
这是如何工作的,为什么我会使用它?
Mark Amery's function比这些更短更清晰,那么它们有什么意义呢?你为什么要用它们?
纯粹为 performance . Mark的回答首先使用unicode字符串完全解码JSON文本,然后通过整个解码值进行递归,将所有字符串转换为字节字符串 . 这有几个不良影响:
整个解码结构的副本在内存中创建
如果您的JSON对象真的是嵌套的(500级或更多),那么'll hit Python'的最大递归深度
此答案通过使用
json.load
和json.loads
的object_hook
参数来缓解这两个性能问题 . 来自the docs:由于字典在其他字典中嵌套了许多级别,因此在它们被解码时会被传递给
object_hook
,我们可以在那时对其中的任何字符串或列表进行字节化,并避免以后需要进行深度递归 .Mark 's answer isn' t适合用作
object_hook
,因为它可以递归到嵌套的词典中 . 我们使用ignore_dicts
参数来阻止该答案中的递归到_byteify
,除非object_hook
将新的dict
传递给byteify,否则它会一直传递给它 .ignore_dicts
标志告诉_byteify
忽略dict
,因为它们已被字节化 .最后,我们的
json_load_byteified
和json_loads_byteified
的实现对从json.load
或json.loads
返回的结果调用_byteify
(带ignore_dicts=True
),以处理被解码的JSON文本在顶层没有dict
的情况 .没有内置选项使json模块函数返回字节字符串而不是unicode字符串 . 但是,这个简短而简单的递归函数会将任何已解码的JSON对象从使用unicode字符串转换为UTF-8编码的字节字符串:
只需在
json.load
或json.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_hook
或object_pairs_hook
参数来避免这些特性 . Mirec Miskuf's answer是迄今为止唯一能够正确解决这个问题的人,尽管如此,它比我的方法复杂得多 .那是因为json在字符串对象和unicode对象之间没有区别 . 它们都是javascript中的所有字符串 .
我想 JSON is right to return unicode objects . 事实上,我不会接受任何更少,因为javascript字符串 are in fact unicode objects (即JSON(javascript)字符串可以存储任何类型的unicode字符)所以在翻译时创建
unicode
对象是有意义的来自JSON的字符串 . 简单的字符串是不适合的,因为库必须猜测你想要的编码 .最好在任何地方使用
unicode
字符串对象 . 因此,您最好的选择是更新库,以便它们可以处理unicode对象 .但是如果你真的想要字节串,只需将结果编码为你选择的编码:
所以,我遇到了同样的问题 . 猜猜Google的第一个结果是什么 .
因为我需要将所有数据传递给PyGTK,所以unicode字符串对我来说也不是很有用 . 所以我有另一种递归转换方法 . 它实际上也需要类型安全的JSON转换 - json.dump()会保留任何非文字,比如Python对象 . 虽然不转换dict索引 .
有一个简单的解决方法 .
TL; DR - 使用
ast.literal_eval()
而不是json.loads()
.ast
和json
都在标准库中 .虽然不是一个“完美”的答案,如果您的计划完全忽略Unicode,它会得到一个很好的答案 . 在Python 2.7中
得到:
当某些对象真的是Unicode字符串时,这会变得更加毛茸茸 . 完整的答案会很快变得毛茸茸 .
正如Mark(Amery)正确指出的那样:在json转储上使用 PyYaml 的反序列化器只有在你只有ASCII时才有效 . 至少开箱即用 .
关于PyYaml方法的两个快速评论:
NEVER对字段中的数据使用yaml.load . 它是yaml的一个特性(!),用于执行隐藏在结构中的任意代码 .
您可以通过以下方式使其适用于非ASCII:
但表现明智,它与马克·阿梅里的回答无可比拟:
把一些深层嵌套的样本dicts扔到两个方法上,我得到了这个(用dt [j] = json.loads的时间增量(json.dumps(m))):
因此反序列化包括完全遍历树和编码,完全在json的基于C的实现的数量级内 . 我发现它非常快,并且它比深嵌套结构中的yaml负载更强大 . 并且更少安全性容易出错,看着yaml.load .
=>虽然我很欣赏指向基于C的转换器的指针,但 byteify function 应该是默认答案 .
如果您的json结构来自包含用户输入的字段,则尤其如此 . 因为那时您可能需要在结构上行走 - 独立于您期望的内部数据结构(仅限'unicode sandwich'或字节字符串) .
为什么?
Unicode normalisation . 不知不觉:服用止痛药并阅读this .
因此,使用byteify递归可以一举两得:
从嵌套的json转储中获取您的字节串
获取规范化的用户输入值,以便您在存储中找到内容 .
在我的测试中,结果发现用unicodedata.normalize('NFC',输入).encode('utf-8')替换input.encode('utf-8')比没有NFC更快 - 但是那我很大程度上依赖于样本数据 .
这是一个用C编写的递归编码器:https://github.com/axiros/nested_encode
与json.load相比,“平均”结构的性能开销约为10% .
使用此测试结构:
Mike Brennan's answer已关闭,但没有理由重新遍历整个结构 . 如果您使用object_hook_pairs(Python 2.7)参数:
有了它,您可以获得每个JSON对象,因此您可以在不需要递归的情况下进行解码:
请注意,我永远不必递归地调用钩子,因为当你使用
object_pairs_hook
时,每个对象都会被传递给钩子 . 您必须关心列表,但正如您所看到的,列表中的对象将被正确转换,您无需递归即可实现 .编辑:一位同事指出Python2.6没有
object_hook_pairs
. 您仍然可以通过进行非常小的更改来使用Python2.6 . 在上面的钩子中,改变:至
然后使用
object_hook
而不是object_pairs_hook
:使用
object_pairs_hook
导致为JSON对象中的每个对象实例化一个较少的字典,如果您正在解析一个巨大的文档,那么可能值得 .我担心在simplejson库中无法自动实现这一点 .
simplejson中的扫描仪和解码器是旨在生成unicode文本 . 为此,库使用名为
c_scanstring
的函数(如果可用,速度为),如果C版本不可用,则使用py_scanstring
. 几乎所有simplejson用于解码可能包含文本的结构的例程都会多次调用scanstring
函数 . 您必须在simplejson.decoder中对scanstring
值进行monkeypatch,或者子类JSONDecoder
并提供几乎所有可能包含文本的实现 .然而,simplejson输出unicode的原因是json spec特别提到"A string is a collection of zero or more Unicode characters" ...对unicode的支持被假定为格式本身的一部分 . Simplejson的
scanstring
实现甚至扫描和解释unicode转义(甚至错误检查格式错误的多字节字符集表示),因此它可以可靠地将值返回给你的唯一方法是unicode .如果您有一个需要
str
的老化库,我建议您在解析后仔细搜索嵌套数据结构(我承认您明确表示要避免...抱歉),或者可能将您的库包装在某种类型中在门面,您可以在更细粒度的水平按摩输入参数 . 如果您的数据结构确实是嵌套的,那么第二种方法可能比第一种方法更易于管理 .问题是
simplejson
和json
是两个不同的模块,至少以它们处理unicode的方式 . 你在py 2.6中有json
,这给你unicode值,而simplejson
返回字符串对象 . 只需在您的环境中尝试easy_install-ing simplejson,看看是否有效 . 它对我有用 .我也遇到了这个问题,并且不得不处理JSON,我想出了一个将unicode键转换为字符串的小循环 . (GAE上的
simplejson
不返回字符串键 . )obj
是从JSON解码的对象:kwargs
是我传递给GAE应用程序的构造函数(它不喜欢**kwargs
中的unicode
键)不像Wells的解决方案那么强大,但要小得多 .
虽然这里有一些很好的答案,但我最终使用PyYAML来解析我的JSON文件,因为它将键和值作为
str
类型字符串而不是unicode
类型 . 因为JSON是YAML的一个子集,所以它可以很好地工作:注意事项
有些事情需要注意:
我得到了字符串对象,因为我的所有条目都是 ASCII encoded . 如果我使用unicode编码的条目,我会把它们作为unicode对象取回 - 没有转换!
你应该(可能总是)使用PyYAML的
safe_load
函数;如果您使用它来加载JSON文件,则无论如何都不需要load
函数的"additional power" .如果你想要一个对规范的1.2版本有更多支持的YAML解析器(和correctly parses very low numbers),请尝试Ruamel YAML:
pip install ruamel.yaml
和import ruamel.yaml as yaml
,这是我在测试中所需要的 .转换
如上所述,没有转换!如果你可以确保大多数时间,请更好地使用 conversion function :
我现在使用了Mark Amery中的那个,它很好用并且非常容易使用 . 您也可以使用与
object_hook
类似的功能,因为它可能会使您在大文件上获得性能提升 . 请参阅稍微复杂一点的answer from Mirec Miskuf .我已经改编了answer的answer中的代码,特别是为了摆脱
isinstance
为鸭子打字的优点 .编码手动完成,并禁用
ensure_ascii
. json.dump的python文档说明了这一点免责声明:在doctest中我使用匈牙利语 . 一些值得注意的与匈牙利相关的字符编码是:
cp852
使用的IBM / OEM编码 . 在DOS中(有时称为ascii,我认为不正确,它取决于代码页设置),例如cp1250
. 在Windows中(有时称为ansi,取决于区域设置)和iso-8859-2
,有时在http服务器上使用 . 测试文本Tüskéshátú kígyóbűvölő
归功于KoltaiLászló(本地个人姓名表),来自wikipedia .我还要强调引用JSON spec的Jarret Hardie的answer,引用:
在我的用例中,我有json的文件 . 它们是
utf-8
编码的文件 .ensure_ascii
导致正确转义但不易读取的json文件,这就是为什么我能够满足我的需求 .doctest不是特别贴心,但我分享了代码,希望它对某人有用 .
这是游戏的后期,但我建造了这个递归的施法者 . 它适用于我的需求,我认为它相对完整 . 它可能会帮助你 .
只需传递一个JSON对象,如下所示:
我把它作为一个类的私有成员,但你可以重新调整方法,如你所见适合 .
我重写了Wells的_parse_json()来处理json对象本身是一个数组的情况(我的用例) .
使用Python 3.6,有时我仍会遇到这个问题 . 例如,当从REST API获取响应并将响应文本加载到JSON时,我仍然获得unicode字符串 . 使用json.dumps()找到了一个简单的解决方案 .