为什么json.loads应该首选ast.literal_eval来解析JSON?

时间:2015-02-09 06:07:55

标签: python json parsing abstract-syntax-tree

我有一个字典作为字符串存储在db字段中。我试图将其解析为dict,但是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'}

6 个答案:

答案 0 :(得分:8)

json.loads失败,因为您的c.iframe_data值不是有效的JSON文档。在有效的json文档字符串中引用了双引号,并且没有像u这样的字符串将字符串转换为unicode。

使用json.loads(c.iframe_data)表示反序列化JSON

中的c.iframe_data文档 只要您需要eval来评估ast.literal_eval表达式,就会使用

input。如果您将Python表达式作为要评估的输入。

  

是否优先于另一个?

这取决于数据。有关更多背景信息,请参阅此answer

答案 1 :(得分:5)

  

我有一个字典,它以字符串形式存储在db字段中。

这是设计错误。虽然完全有可能像有人所做的那样提取字典的repr,但不能保证可以完全评估对象的repr

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

  

我正在尝试将其解析为字典,但是json.loads给我一个错误。

自然。您没有在数据库中存储JSON,因此期望将其解析为JSON似乎并不合理。有趣的是,可以使用ast.literal_eval来解析值,但是除了相对简单的Python类型之外,没有其他保证。

由于看来您的数据确实仅限于此类类型,所以解决您问题的真正方法是通过在存储到数据库之前使用json.dumps将字典转换为字符串来纠正数据的存储方式。某些数据库系统(例如 ,PostgreSQL)具有JSON类型,以简化对此类数据的查询,我建议您使用可用的此类类型。

关于“更好”,这将始终取决于特定的应用程序,但是JSON被明确设计为一种紧凑的人类可读的机器可解析的格式,用于简单的结构化数据,而您当前的表示形式则基于特定于Python(例如)很难用其他语言进行评估。 JSON是此处适用的标准,您将从使用它中受益。

答案 2 :(得分:2)

因为u"{u'person': u'Annabelle!', u'csrfmiddlewaretoken': u'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}"是一个Python unicode字符串,而不是一个Javascript对象表示法,在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"'}

答案 3 :(得分:2)

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

答案 4 :(得分:0)

首先,最重要的是,不要序列化数据两次。您的数据库本身就是数据的序列化,具有用于查询,浏览,操作和显示数据的丰富且富有表现力的工具集。序列化数据以便随后放入数据库中,从而消除了隔离子组件更新,子组件查询和索引的可能性,并将所有写入与强制性初始读取耦合在一起,解决了一些最重要的问题。

接下来,Java Script Object Notation (JSON)是JavaScript语言的有限子集,适用于在数据交换服务中表示静态数据。作为语言的子集,这意味着您可以在JS中幼稚地eval来重构原始对象。这是一个简单的序列化操作(没有内部引用,模板定义,类型扩展之类的高级功能),具有JavaScript语言的局限性以及因使用需要大量“转义”的字符串而受到的处罚。结束标记的使用也使得难以在纯流式传输方案中使用,例如您必须先敲击成对的}才能“完成”对象,因此它也没有用于记录分离的标记。其他局限性的显着例子包括在JSON中传递HTML,这需要过度转义,所有数字均为浮点数(54位整数精度,舍入误差等),使其显然不适合金融信息的存储或传输或技术使用(例如,加密) )需要64位整数,没有本地日期表示形式,...

作为语言的JS和Python之间存在一些显着差异,因此在JSON“ JavaScript Object Notation”与PLS(Python文字语法)的行为上也有一些区别。碰巧的是,出于文字定义的目的,大多数JavaScript文字语法都与Python直接兼容,尽管其解释略有不同。反之则不正确,请参见上面的差异示例。如果您关心为Python保留数据的保真度,则Python文字比同等的JS更具表达力,并且“损失”更少。但是,正如其他答案/评论所指出的那样,repr()不是生成此表示形式的可靠方法; Python文字语法不是 meant 那样使用。为了获得最大的保真度,我通常建议使用YAML序列化,其中JSON是完全有效的子集。

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

答案 5 :(得分:0)

出于以下所有原因,

json.loads最好比ast.literal_eval解析JSON (总结其他张贴者)。

在您的特定示例中,无论如何,使用Python 2.x,您的输入都是非法/格式错误的JSON,使用Python 2.x(所有不需要的和非法u'前缀)以错误的方式导出。本身接近EOL,请移至3.x。您可以简单地使用正则表达式来修复/预处理以下内容:

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

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

>>> json.loads(legal_json)
{'person': 'Annabelle!', 'csrfmiddlewaretoken': 'wTE9RZGvjCh9RCL00pLloxOYZItQ98JN'}
  • (注意:如果您的体系结构中有许多格式错误的JSON字符串以错误的方式从2.x导出并存储在数据库中,则不是使用json.loads的合法理由,而是重新访问您的体系结构。至少只需对所有字符串运行一次fixup regex,然后将合法的JSON存储回去))

json.loads优点/缺点:

  • 处理所有合法的JSON,与ast.literal_eval

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

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