【问题标题】:JSONDecodeError "Expecting property name enclosed in double quotes" from file with multiple JSON documents来自具有多个 JSON 文档的文件的 JSONDecodeError“期望属性名称用双引号括起来”
【发布时间】:2018-04-04 23:08:23
【问题描述】:

在 Google 上到处查看,但没有成功找到解决此问题的方法,我继续收到以下错误:

JSONDecodeError: Expecting property name enclosed in double quotes: line 2 column 1 (char 2)

错误发生在我的 Python 文件中的 row = json.loads(row) 行。 JSON 文件包含来自 2015-05 的 Reddit cmets 的一部分:

JSON (learn\learning_data\2015\RC_2015-05):

{
  "created_utc": "1430438400",
  "ups": 4,
  "subreddit_id": "t5_378oi",
  "link_id": "t3_34di91",
  "name": "t1_cqug90g",
  "score_hidden": false,
  "author_flair_css_class": null,
  "author_flair_text": null,
  "subreddit": "soccer_jp",
  "id": "cqug90g",
  "removal_reason": null,
  "gilded": 0,
  "downs": 0,
  "archived": false,
  "author": "rx109",
  "score": 4,
  "retrieved_on": 1432703079,
  "body": "\u304f\u305d\n\u8aad\u307f\u305f\u3044\u304c\u8cb7\u3063\u305f\u3089\u8ca0\u3051\u306a\u6c17\u304c\u3059\u308b\n\u56f3\u66f8\u9928\u306b\u51fa\u306d\u30fc\u304b\u306a",
  "distinguished": null,
  "edited": false,
  "controversiality": 0,
  "parent_id": "t3_34di91"
}

*JSON 数据只是我实际拥有的一小部分,我无法更改格式。 例如。

{
  "text": "data",
  "text": "data"
}
{
  "text2": "data",
  "text2": "data"
}
{
  "text3": "data",
  "text3": "data"
}
etc...

Python (learn\main.py):

with open("learning_data/{}/RC_{}".format(timeframe.split('-')[0], timeframe), buffering=1000) as f:
for row in f:
    row_counter += 1
    row = json.loads(row)
    body = format_data(row['body'])
    created_utc = row['created_utc']
    parent_id = row['parent_id']
    comment_id = row['name']
    score = row['score']
    subreddit = row['subreddit']       
    parent_data = find_parent(parent_id)

    if score >= 2:
        if acceptable(body):
            existing_comment_score = find_existing_score(parent_id)

JSON 文件已经在需要双引号的所有内容上添加了双引号。 如果有其他错误导致这个 JSONLint.com 声称 JSON 没有它们。

我一直在从this tutorial 引用我的代码,其中教程的代码运行良好,没有任何错误(这是根据附加的视频,使用上面链接中的代码,我仍然得到错误)。因为教程使用的是 Python 3.5,所以我降级了我的 Python 版本并继续得到同样的错误。

如果 JSON 已经使​​用双引号并且由 JSONLint 有效,那么导致此错误的原因是什么?

【问题讨论】:

  • 您不必为每个 JSON 行调用 json.loads。您需要传递完整的 JSON 作为参数,例如,json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
  • 我拥有的 JSON 数据无法更改,我添加了一个示例,说明它目前的样子。

标签: python json


【解决方案1】:

每行一个 JSON 文档流是一种也称为 JSONL 的格式。这与“JSON”不同,后者只允许一个文档到一个文件中。

您可以通过运行jq -c . <in.json >out.json 轻松地将文件转换为这种格式。 jq 是一个命令行工具,用于处理 JSON 和 JSONL 文档; -c 标志启用“紧凑”模式,将每个文档放在输出的每一行。

更简单的是,您可以在线完成,让您的 Python 代码直接迭代 jq 的输出:

import subprocess

with open("learning_data/{}/RC_{}".format(timeframe.split('-')[0], timeframe)) as f:
    p = subprocess.Popen(['jq', '-c', '.'], stdin=f, stdout=subprocess.PIPE)
    for line in p.stdout:
        content = json.loads(line)
        # ...process your line's content here.

【讨论】:

  • 子进程找不到 JSON 文件,不知道为什么。仔细检查路径并将RC_2015-05重命名为RC_2015-05.json,仍然找不到指定的文件。
  • 嗯? subprocess 不应该尝试来查找 JSON 文件。在运行任何 subprocess 代码之前,您应该已经打开了 JSON 文件。 subprocess 应该尝试查找的唯一内容是名为 jq 的命令,如果没有安装,则应该安装它。
  • 我目前没有安装jq,安装jq时出错:error: [WinError 2] The system cannot find the file specified
  • “安装jq”怎么样? (并不是说这在 Python 标记问题的范围内......或在 StackOverflow 上,就此而言;“如何在我的操作系统上安装软件?”更像是一个 SuperUser 主题)。
  • 对不起,我误读了您的评论,认为 jq 是一个 python 包。我收到以下错误:parse error: Unfinished string at EOF at line 603195, column 263 Error: writing output failed: Invalid argument
【解决方案2】:

您的 JSON 中有换行符。

但是您的代码一次读取一行并期望它是完整的 JSON 文本:

for row in f:
    row_counter += 1
    row = json.loads(row)

那是行不通的。

如果您的文件只是一个 JSON 文本,只需阅读整个内容:

with open("learning_data/{}/RC_{}".format(timeframe.split('-')[0], timeframe), buffering=1000) as f:
    row_counter += 1
    row = json.load(f)

您可能希望将row 重命名为更有意义的名称,例如contents


如果您的文件一系列 JSON 文本,并且您自己生成文件,那么正确的做法是更改生成它的方式。任意 JSON 文本流并不是真正有效的格式。但是如果你真的想在此基础上构建一个格式,你可以——例如,转义所有的换行符,以便你可以逐行解析它。或者您可以使用真实格式。或者你可以只写出一个大的 JSON 数组而不是一堆单独的 JSON 文本。


如果你不能改变文件,你需要一个策略来解析它。所有这些都几乎正确:

  • 使用json模块的raw_decode方法读取下一个JSON文本并返回解码后的值加上下一个的偏移量。
  • 每次计数为 0 时平衡括号和大括号并拆分。
  • 扫描换行符,然后回溯以检查左括号和大括号。

除了糟糕的错误处理之外,其中任何一个的唯一严重问题是它们不可能对作为顶级文本的数字做正确的事情。如果您的顶级文本都是对象,那不是问题。

所以:

with open("learning_data/{}/RC_{}".format(timeframe.split('-')[0], timeframe), buffering=1000) as f:
    contents = f.read()
    decoder = json.JSONDecoder()
    while contents:
        row, idx = decoder.raw_decode(contents)
        row_counter += 1
        contents = contents[idx:].lstrip()
        # etc.

虽然如果你的文件很大,你几乎肯定想要mmap 并将切片/内存视图传递给raw_decode — 或者,如果这不起作用,因为你有 Unicode 文本,你可能需要缓冲手动分块。不完全是微不足道的,但是您正在解析损坏的格式,因此这比您预期的要容易。 :)

【讨论】:

  • "如果您的文件只是一个 JSON 文本,请阅读整个内容:"我将有更多 JSON 文本,{ "text": "data"}{"text2": "data"},我只是不想添加 600,000+ 行JSON 代码。 Reddit 数据是(如果我是正确的)“任意 JSON 文本流”,这将永远“纠正格式”。截至目前,我无法更改 JSON 数据。
  • @TomJaquo 然后按照我的回答建议去做。
  • 您的最后一个代码似乎只循环通过我拥有的 JSON 代码的第一部分,可能是因为我不得不注释掉 del contents[:idx] ,因为我收到以下错误:'str' object does not support item deletion
  • @TomJaquo 是的,很明显,只是注释掉跳到下一个 JSON 文本的代码意味着你不会跳到下一个 JSON 文本。无论如何,我正在使用bytearray 而不是str 进行测试;我已经编辑了应该与 str 一起使用的答案。
  • @TomJaquo 但是正如我在答案中提到的,如果您的实际输入确实太大而无法修复,那么将整个内容读入 str,解码并将其切碎可能太大了,并且您确实需要执行类似 mmap 文件的操作。 (虽然我怀疑它真的 太大了;600K 行听起来可能很多,但对于现代计算机来说确实不算多。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-24
  • 2017-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-25
相关资源
最近更新 更多