为什么 json.loads 比 ast.literal_eval 更适合解析 JSON?

Why should json.loads be preferred to ast.literal_eval for parsing JSON?

我有一个字典,它作为字符串存储在数据库字段中。我试图将它解析成字典,但 json.loads 给我一个错误。

为什么 json.loads 失败而 ast.literal_eval 有效?一个比另一个更可取吗?

>>> c.iframe_data
u"{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}"

# json fails
>>> json.loads(c.iframe_data)
Traceback (most recent call last):
ValueError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

# ast.literal_eval works
>>> ast.literal_eval(c.iframe_data)
{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}

json.loads 失败,因为您的 c.iframe_data 值不是有效的 JSON 文档。在有效的 json 文档中,字符串用双引号引起来,没有类似 u 的东西用于将字符串转换为 unicode。

使用json.loads(c.iframe_data)意味着在c.iframe_data

中反序列化JSON文档

ast.literal_eval 在需要 eval 来评估 input 表达式时使用。如果您有 Python 个表达式作为要计算的输入。

Is one preferable over the other?

这取决于数据。有关更多上下文,请参阅此

因为 u"{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}" 是 Python unicode 字符串,而不是 Javascript Object Notation ,在 chrome 控制台中:

bad = {u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
SyntaxError: Unexpected string
good = {'person': 'Annabelle!', 'csrfmiddlewaretoken': 'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
Object {person: "Annabelle!", csrfmiddlewaretoken: "wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"}

或者你可以使用yaml来处理:

>>> a = '{"person": "Annabelle!", "csrfmiddlewaretoken": "wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"}'
>>> json.loads(a)
{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
>>> import ast
>>> ast.literal_eval(a)
{'person': 'Annabelle!', 'csrfmiddlewaretoken': 'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
>>> import yaml
>>> a = '{u"person": u"Annabelle!", u"csrfmiddlewaretoken": u"wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"}'
>>> yaml.load(a)
{'u"person"': 'u"Annabelle!"', 'u"csrfmiddlewaretoken"': 'u"wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"'}
>>> a = u'{u"person": u"Annabelle!", u"csrfmiddlewaretoken": u"wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"}'
>>> yaml.load(a)
{'u"person"': 'u"Annabelle!"', 'u"csrfmiddlewaretoken"': 'u"wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"'}

json.loads 专门用于解析 JSON 这种非常严格的格式。没有 u'...' 语法,所有字符串都用双引号分隔,而不是单引号。使用 json.dumps 序列化可以被 json.loads.

读取的内容

所以 json.loads(string)json.dumps(object) 的倒数,而 ast.literal_eval(string) 是(模糊地)repr(object) 的倒数。

JSON 很好,因为它是可移植的——几乎每种语言都有针对它的解析器。因此,如果您想将 JSON 发送到 Javascript 前端,您不会有任何问题。

ast.literal_eval 不易移植,但它稍微丰富一些:例如,您可以使用键不限于字符串的元组、集合和字典。

还有json.loads is significantly faster than ast.literal_eval.

I have a dictionary that is stored in a db field as a string.

这是一个设计错误。虽然完全有可能提取字典的 repr,但完全有可能提取字典的 repr,但不能保证对象的 repr 可以被求值。

在仅存在字符串键以及字符串和数字值的情况下,大多数情况下 Python eval 函数会从其 repr 中重现该值,但我不确定您为什么认为这会例如,使其有效 JSON。

I am trying to parse it into a dict, but json.loads gives me an error.

当然。您没有将 JSON 存储在数据库中,因此期望它解析为 JSON 似乎不太合理。虽然有趣的是 ast.literal_eval 可用于解析该值,但是除了相对简单的 Python 类型之外也没有任何保证。

由于您的数据似乎确实仅限于此类类型,因此解决问题的真正方法是更正数据的存储方式,方法是在存储之前将字典转换为带有 json.dumps 的字符串数据库。一些数据库系统(例如、PostgreSQL)有JSON类型来简化查询此类数据,我建议您使用这些类型(如果可用)。

至于哪个 "better," 将始终取决于特定的应用程序,但是 JSON 被明确设计为用于简单结构化数据的紧凑的人类可读机器可解析格式,而您当前的表示基于特定于 Python 的格式,(例如)在其他语言中评估起来会非常乏味。 JSON是这里适用的标准,你会从中受益。

首先,也是最重要的一点,不要将数据序列化两次。您的数据库本身就是数据的序列化,具有一组丰富且富有表现力的工具来查询、探索、操作和呈现数据。序列化数据随后放入数据库消除了孤立的子组件更新、子组件查询和索引的可能性,并将所有写入耦合到强制性初始读取,以解决一些最重要的问题。

接下来,Java Script Object Notation (JSON) 是 JavaScript 语言的一个有限子集,适用于在数据交换服务中表示静态数据。作为语言的子集,这意味着你可以天真地eval它在JS中重构原始对象。这是一个简单的序列化(没有内部引用、模板定义、类型扩展等高级功能),具有 JavaScript 语言的局限性以及使用需要大量 "escaping" 的字符串的惩罚。结束标记的使用也使得它难以在纯流媒体场景中使用,例如你不能 "finalize" 一个对象,直到击中它的配对 },因此它也没有记录分离的标记。其他限制的显着示例包括在 JSON 内传递 HTML 需要过度转义,所有数字都是浮点数(54 位整数精度,舍入误差,......)使其显然不适合存储或传输财务信息信息或技术(例如加密)的使用需要 64 位整数,没有本地日期表示,...

作为语言,JS 和 Python 之间存在一些显着差异,因此 JSON "JavaScript Object Notation" 与 PLS(Python 文字语法)的行为方式也存在显着差异。碰巧的是,出于字面定义的目的,大多数 JavaScript 字面语法直接与 Python 兼容,尽管解释略有不同。反之则不然,参见上面的差异示例。如果您关心为 Python 保留数据的保真度,Python 文字比它们的 JS 等价物更具表现力并且 "lossy" 更少。但是,正如其他 answers/comments 指出的那样,repr() 不是生成此表示的可靠方法; Python 文字语法 并不意味着 以这种方式使用。为了获得最大的类型保真度,我通常推荐 YAML 序列化,其中 JSON 是一个完全有效的子集。

仅供参考,为了解决存储与实体关联的类字典映射的实际问题,有 entity-attribute-value data models。关系数据库 FTW 中的任意键值存储,但权力伴随着责任。谨慎使用此模式,仅在绝对需要时才使用。 (如果这是一个常见的模式,请查看文档存储。)

json.loads 在解析 JSON 时应优先于 ast.literal_eval,原因如下(总结其他发帖人)。

在您的具体示例中,您的输入是 illegal/malformed JSON 使用 Python 2.x 以错误的方式导出(全部不需要的和非法的 u' 前缀),无论如何 Python 2.x 本身就接近停产,请移至 3.x。您可以简单地使用正则表达式 fixup/preprocess 即:

>>> import json
>>> import re
>>> malformed_json = u"{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}"

>>> legal_json = re.sub(r'u\'([^\']*)\'', r'""', malformed_json)
'{"person": "Annabelle!", "csrfmiddlewaretoken": "wTE9RZGvjCh9RCL00pLloxOYZItQ98JN"}'

>>> json.loads(legal_json)
{'person': 'Annabelle!', 'csrfmiddlewaretoken': 'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
  • (注意:如果您的体系结构有很多格式错误的 JSON 字符串以错误的方式从 2.x 导出,存储在数据库中,那么这不是不使用 json.loads 的合法理由, 但它是重新审视你的架构。至少只是 运行 所有字符串上的修正正则表达式,一次,并将合法的 JSON 存储回来))

json.loads Pros/Cons:

  • 处理所有合法的 JSON,不同于 ast.literal_eval

  • 慢。 有更快的 JSON 库,例如 ultrajson, yajl, simplejson 等。此外,在大型导入作业中,您可以使用 multiprocessing/multithreading(这也可以防止内存泄漏,这是所有解析器的常见问题)。

  • 数字字段: 将所有整数、长整数和浮点数转换为双精度,可能会丢失精度 (@amcgregor)