关于python:显示比“没有JSON对象可以解码”更好的错误消息

Displaying better error message than “No JSON object could be decoded”

从一些长而复杂的JSON文件加载数据的python代码:

1
2
with open(filename,"r") as f:
  data = json.loads(f.read())

(注意:最好的代码版本应该是:

1
2
with open(filename,"r") as f:
  data = json.load(f)

但两者表现出相似的行为)

对于许多类型的JSON错误(缺少分隔符、字符串中不正确的反斜杠等),这将打印一条很好的有用消息,其中包含发现JSON错误的行号和列号。

但是,对于其他类型的JSON错误(包括经典的"在列表中的最后一个项目上使用逗号",以及其他类似大写"真/假"),python的输出只是:

1
2
3
4
5
6
7
8
9
10
Traceback (most recent call last):
  File"myfile.py", line 8, in myfunction
    config = json.loads(f.read())
  File"c:\python27\lib\json\__init__.py", line 326, in loads
    return _default_decoder.decode(s)
  File"c:\python27\lib\json\decoder.py", line 360, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File"c:\python27\lib\json\decoder.py", line 378, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

对于这种类型的valueerror,如何让python告诉您JSON文件中的错误在哪里?


我发现,在许多情况下,simplejson模块提供了更多的描述性错误,其中内置的json模块是模糊的。例如,对于列表中最后一项后面有逗号的情况:

1
2
3
json.loads('[1,2,]')
....
ValueError: No JSON object could be decoded

不是很有描述性。与simplejson相同的操作:

1
2
3
simplejson.loads('[1,2,]')
...
simplejson.decoder.JSONDecodeError: Expecting object: line 1 column 5 (char 5)

好多了!同样,对于其他常见错误,如资本化True


您将无法让Python告诉您JSON在哪里不正确。你需要在网上这样的地方使用一个线头。

这将显示您试图解码的JSON中的错误。


你可以试试这里的rson库:http://code.google.com/p/rson/。在pypi上也有:https://pypi.python.org/pypi/rson/0.9,这样您就可以使用easy-install或pip来获取它。

对于Tom给出的示例:

1
2
3
>>> rson.loads('[1,2,]')
...
rson.base.tokenizer.RSONDecodeError: Unexpected trailing comma: line 1, column 6, text ']'

rson被设计成JSON的超集,因此它可以解析JSON文件。它还有另一种语法,对人类来说,这种语法更适合观察和编辑。我经常用它来输入文件。

至于布尔值的大写:似乎rson将错误大写的布尔值作为字符串读取。

1
2
>>> rson.loads('[true,False]')
[True, u'False']

我有一个类似的问题,这是由于单引号。JSON标准(http://json.org)只讨论使用双引号,所以必须是python json库只支持双引号。


我也有类似的问题,这是我的代码:

1
2
3
4
5
6
7
    json_file=json.dumps(pyJson)
    file = open("list.json",'w')
    file.write(json_file)  

    json_file = open("list.json","r")
    json_decoded = json.load(json_file)
    print json_decoded

问题是我忘了把问题交给埃多克斯,我做到了,解决了问题。


对于这个问题的具体版本,我在packaging.py文件中搜索了load_json_file(path)的功能声明,然后走私了一条print行:

1
2
3
4
5
6
7
8
def load_json_file(path):
    data = open(path, 'r').read()
    print data
    try:
        return Bunch(json.loads(data))
    except ValueError, e:
        raise MalformedJsonFileError('%s when reading"%s"' % (str(e),
                                                               path))

这样,它就可以在进入try catch之前打印JSON文件的内容,这样,即使我几乎不了解Python,我也能很快理解为什么我的配置不能读取JSON文件。(这是因为我设置了文本编辑器来编写一个utf-8bom…太蠢了)

仅仅提到这一点是因为,虽然这可能不是一个很好的答案,但对于确定一个非常令人压抑的bug的来源来说,这是一个相当快速的方法。我敢打赌,许多人会偶然发现这篇文章,他们正在为MalformedJsonFileError: No JSON object could be decoded when reading …寻找更详细的解决方案。所以这可能对他们有帮助。


对于我来说,我的JSON文件非常大,在python中使用通用的json时,会得到上面的错误。

安装完simplejson后,由sudo pip install simplejson安装。

然后我就解决了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
import simplejson


def test_parse_json():
    f_path = '/home/hello/_data.json'
    with open(f_path) as f:
        # j_data = json.load(f)      # ValueError: No JSON object could be decoded
        j_data = simplejson.load(f)  # right
    lst_img = j_data['images']['image']
    print lst_img[0]


if __name__ == '__main__':
    test_parse_json()

创建文件时。而不是创建内容为空的文件。替换为:

1
json.dump({}, file)

只是碰到了同样的问题,在我的例子中,这个问题与文件开头的EDOCX1(字节顺序标记)有关。

json.tool会拒绝处理甚至是空文件(只是大括号),直到我删除了utf-bom标记。

我所做的是:

  • 用vim打开了我的json文件,
  • 删除的字节顺序标记(set nobomb)
  • 保存文件

这就解决了json.tool的问题。希望这有帮助!


公认的答案是最容易解决问题的答案。但是,如果由于您的公司政策,您不能安装simplejson,我建议下面的解决方案来解决"在列表中的最后一项上使用逗号"的特定问题:

  • 创建一个子类"jsonIntCheck"以从类"jsonDecoder"继承并重写类"jsonDecoder"的init方法,如下所示:

    1
    2
    3
    def __init__(self, encoding=None, object_hook=None, parse_float=None,parse_int=None, parse_constant=None, strict=True,object_pairs_hook=None)        
            super(JSONLintCheck,self).__init__(encoding=None, object_hook=None,      parse_float=None,parse_int=None, parse_constant=None, strict=True,object_pairs_hook=None)
            self.scan_once = make_scanner(self)
  • make_scanner是一个新函数,用于重写上述类的"scan_once"方法。这是它的代码:
  • 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
      1 #!/usr/bin/env python
      2 from json import JSONDecoder
      3 from json import decoder
      4 import re
      5
      6 NUMBER_RE = re.compile(
      7     r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
      8     (re.VERBOSE | re.MULTILINE | re.DOTALL))
      9
     10 def py_make_scanner(context):
     11     parse_object = context.parse_object
     12     parse_array = context.parse_array
     13     parse_string = context.parse_string
     14     match_number = NUMBER_RE.match
     15     encoding = context.encoding
     16     strict = context.strict
     17     parse_float = context.parse_float
     18     parse_int = context.parse_int
     19     parse_constant = context.parse_constant
     20     object_hook = context.object_hook
     21     object_pairs_hook = context.object_pairs_hook
     22
     23     def _scan_once(string, idx):
     24         try:
     25             nextchar = string[idx]
     26         except IndexError:
     27             raise ValueError(decoder.errmsg("Could not get the next character",string,idx))
     28             #raise StopIteration
     29
     30         if nextchar == '"':
     31             return parse_string(string, idx + 1, encoding, strict)
     32         elif nextchar == '{':
     33             return parse_object((string, idx + 1), encoding, strict,
     34                 _scan_once, object_hook, object_pairs_hook)
     35         elif nextchar == '[':
     36             return parse_array((string, idx + 1), _scan_once)
     37         elif nextchar == 'n' and string[idx:idx + 4] == 'null':
     38             return None, idx + 4
     39         elif nextchar == 't' and string[idx:idx + 4] == 'true':
     40             return True, idx + 4
     41         elif nextchar == 'f' and string[idx:idx + 5] == 'false':
     42             return False, idx + 5
     43
     44         m = match_number(string, idx)
     45         if m is not None:
     46             integer, frac, exp = m.groups()
     47             if frac or exp:
     48                 res = parse_float(integer + (frac or '') + (exp or ''))
     49             else:
     50                 res = parse_int(integer)
     51             return res, m.end()
     52         elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
     53             return parse_constant('NaN'), idx + 3
     54         elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
     55             return parse_constant('Infinity'), idx + 8
     56         elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
     57             return parse_constant('-Infinity'), idx + 9
     58         else:
     59             #raise StopIteration   # Here is where needs modification
     60             raise ValueError(decoder.errmsg("Expecting propert name enclosed in double quotes",string,idx))
     61     return _scan_once
     62
     63 make_scanner = py_make_scanner
  • 最好将"make_scanner"函数和新的子类放在同一个文件中。

  • 您可以使用cjson,它声称比纯python实现快250倍,因为您有"一些长期复杂的json文件",您可能需要多次运行它(解码器失败并报告它们遇到的第一个错误)。