【问题标题】:How to parse json file with c-style comments?如何解析带有 c 样式注释的 json 文件?
【发布时间】:2018-08-09 15:30:59
【问题描述】:

我有一个json文件,如下:

    { 
       "author":"John",
       "desc": "If it is important to decode all valid JSON correctly \ 
and  speed isn't as important, you can use the built-in json module,   \
 orsimplejson.  They are basically the same but sometimes simplej \
further along than the version of it that is included with \
distribution."
       //"birthday": "nothing" //I comment this line
    }

这个文件是由另一个程序自动创建的。如何用 Python 解析它?

【问题讨论】:

  • 句柄是什么意思??
  • 那不是 JSON,所以你必须扩展一个 JSON 解析器(或者写一个新的;它不是那么复杂的格式......)。
  • 另外,这些是斜杠,而不是反斜杠。
  • 这意味着我必须在调用标准 json 解析器之前更改其样式?
  • 您与“另一个程序”的供应商交谈并要求他们提供 JSON。那不是 JSON,因为 JSON 不支持 cmets。

标签: python json


【解决方案1】:

jsoncomment 不错,但是不支持内联注释。

查看jstyleson,支持

  • 内嵌评论
  • 单行注释
  • 多行注释
  • 尾随逗号。

评论被保留。 jstyleson 首先删除所有 cmets 和尾随逗号,然后使用标准的 json 模块。似乎函数参数被转发并按预期工作。它还公开了dispose 以在不解析的情况下返回已清理的字符串内容。

示例

安装

pip install jstyleson

用法

import jstyleson
result_dict = jstyleson.loads(invalid_json_str) # OK
jstyleson.dumps(result_dict)

【讨论】:

  • 需要明确指出你是jstyleson的作者。我认为这篇文章还可以,因为它是解决 OP 问题的一种方式,但除非明确指出,否则自我广告通常不受欢迎。
【解决方案2】:

我建议大家改用 JSON5 库。 JSON5 是带有 JavaScript 特性/支持的 JSON。它是世界上最流行的 JSON 语言扩展。它有 cmets,支持对象/数组中的尾随逗号,支持单引号键/字符串,支持不带引号的对象键等。并且有适当的解析器库和深度测试套件,一切都运行良好。

有两种不同的高质量 Python 实现:

这是 JSON5 规范:https://json5.org/

【讨论】:

    【解决方案3】:

    我没有亲自使用过,但是jsoncommentpython包支持用cmets解析JSON文件。

    您可以使用它来代替 JSON 解析器,如下所示:

    parser = JsonComment(json)
    parsed_object = parser.loads(jsonString)
    

    【讨论】:

    • 这个包只在行首去除 cmets。所以你无法解析[1,2,3,/* a comment */ 10]
    • JsonComment 通过简单替换删除尾随逗号(因此它删除包含 ,] 或 ,} 的字符串)。此外,如果它们后面有空格,它不会删除尾随逗号。
    【解决方案4】:

    我无法想象一个json文件“由其他程序自动创建”里面会包含cmets。因为json spec根本没有定义注释,也就是by design,所以没有json库会输出带注释的json文件。

    这些 cmets 通常是稍后由人类添加的。在这种情况下也不例外。 OP 在他的帖子中提到了这一点://"birthday": "nothing" //I comment this line

    所以真正的问题应该是,我如何正确地注释 json 文件中的某些内容,同时保持其符合规范,从而保持与其他 json 库的兼容性?

    答案是,将您的字段重命名为另一个名称。示例:

    {
        "foo": "content for foo",
        "bar": "content for bar"
    }
    

    可以改成:

    {
        "foo": "content for foo",
        "this_is_bar_but_been_commented_out": "content for bar"
    }
    

    这在大多数情况下都可以正常工作,因为消费者很可能会忽略意外字段(但并非总是如此,这取决于您的 json 文件消费者的实现。所以 YMMV。)

    更新:显然有些读者不高兴,因为这个答案没有给出他们期望的“解决方案”。好吧,事实上,我确实给出了一个可行的解决方案,隐式链接到JSON designer's quote

    Douglas Crockford 公开发表于 2012 年 4 月 30 日 JSON 格式的评论

    我从 JSON 中删除了 cmets,因为我看到人们使用它们来 持有解析指令,这种做法会破坏 互操作性。我知道缺少cmets会让一些人 伤心,但不应该。

    假设您使用 JSON 来保存配置文件, 想注释。继续并插入您喜欢的所有 cmets。 然后通过 JSMin 将其传递给 JSON 解析器。

    所以,是的,继续使用JSMin。请记住,当您走向“在 JSON 中使用 cmets”时,这是一个概念上未知的领域。无法保证您选择的任何工具都能处理:内联 [1,2,3,/* a comment */ 10]、Python 样式 [1, 2, 3] # a comment(这是 Python 中的注释,但不是 Javascript 中的注释)、INI 样式 [1, 2, 3] ; a comment,...,您明白了。

    我仍然建议首先不要在 JSON 中添加不合规的 cmets。

    【讨论】:

    • tsc --init (typescript) 用我相信的 cmets 生成一个tsconfig.json
    • phpmyadmin JSON 导出器在 /* */// 表单中添加 cmets。
    • @RayLuo:我不希望这个 cmets 部分变成无用的玩笑,所以:1)我添加了 my own answer clarifying what you chose not to 和 2)关于“普遍同意”让我指出你给这些鲜为人知的代码编辑器:Sublime Text、Atom、VS Code(它们都使用 JSON 进行配置),让事情就此结束
    • 我真的无法接受应该删除功能的心态,因为它们可能被滥用。多亏了这一点,我们现在有大量相互竞争的 JSON 替代方案,因为纯 JSON 不支持常见且合理的用例。对配置文件进行预处理,或者必须“构建”您的配置并不是一种明智的方法,它只会增加阻抗。它使简单的事情变得困难,这与我们应该努力实现的目标相反。
    • 是的。应该注意的是,向 HTML 添加 cmets 并没有阻止那里的互操作性。您还可以使用尾随空格向解析器发出提示,但这不是不允许的。作为对人类作者的让步,空白是灵活的。就我个人而言,我认为 JSON 介于两种情况之间:它是一种有线格式(不允许使用 cmets),但专为人类编辑而设计(空白灵活)。我确实希望有朝一日会达成一项允许 cmets 的协议,但到那时,繁琐的工具和库将需要数年时间才能赶上。
    【解决方案5】:

    commentjson 怎么样?

    http://commentjson.readthedocs.io/en/latest/

    这可以解析如下内容。

    {
        "name": "Vaidik Kapoor", # Person's name
        "location": "Delhi, India", // Person's location
    
        # Section contains info about
        // person's appearance
        "appearance": {
            "hair_color": "black",
            "eyes_color": "black",
            "height": "6"
        }
    }
    

    可能是 elasticsearch,部分产品的 REST API 不接受评论字段。因此,我认为 json 中的注释对于客户端来说是必要的,以便维护诸如 json 模板。


    已编辑

    jsmin 似乎更常见。

    https://pypi.python.org/pypi/jsmin

    【讨论】:

      【解决方案6】:

      简而言之:使用jsmin

      pip 安装 jsmin

      import json
      from jsmin import jsmin
      
      with open('parameters.jsonc') as js_file:
          minified = jsmin(js_file.read())
      parameters  = json.loads(minified)
      

      【讨论】:

        【解决方案7】:

        如果你和我一样喜欢避免使用外部库,我写的这个函数将从文件中读取 json 并删除“//”和“/* */”类型的 cmets:

        def GetJsonFromFile(filePath):
            contents = ""
            fh = open(filePath)
            for line in fh:
                cleanedLine = line.split("//", 1)[0]
                if len(cleanedLine) > 0 and line.endswith("\n") and "\n" not in cleanedLine:
                    cleanedLine += "\n"
                contents += cleanedLine
            fh.close
            while "/*" in contents:
                preComment, postComment = contents.split("/*", 1)
                contents = preComment + postComment.split("*/", 1)[1]
            return contents
        

        限制:正如 David F. 在 cmets 中提出的,这将在字符串文字中使用 ///* 完美地打破(即:可怕)。如果您想在 json 字符串内容中支持 ///**/,则需要围绕它编写一些代码。

        【讨论】:

        • 请注意,此实现将错误地将字符串文字中的“//”和“/*”识别为注释开始标记,并且在这种情况下会产生奇怪的结果。
        • 确实!感谢您提出这个问题。
        【解决方案8】:

        如果您并不真正关心严格的 JSON 格式,而只是想要一些允许您在 JSON 中使用 cmets 的东西,您可能会查看 Json5。例如,这个库可以让你解析 JSON5:https://pypi.org/project/json5/

        【讨论】:

          【解决方案9】:

          这是一个小的独立包装器:

          #!/usr/bin/env python3
          import json
          import re
          
          def json_load_nocomments( filename_or_fp, comment = "//|#", **jsonloadskw ) -> "json dict":
              """ load json, skipping comment lines starting // or #
                  or white space //, or white space #
              """
              # filename_or_fp -- lines -- filter out comments -- bigstring -- json.loads
          
              if hasattr( filename_or_fp, "readlines" ):  # open() or file-like
                  lines = filename_or_fp.readlines()
              else:
                  with open( filename_or_fp ) as fp:
                      lines = fp.readlines()  # with \n
              iscomment = re.compile( r"\s*(" + comment + ")" ).match
              notcomment = lambda line: not iscomment( line )  # ifilterfalse
              bigstring = "".join( filter( notcomment, lines ))
                  # json.load( fp ) does loads( fp.read() ), the whole file in memory
          
              return json.loads( bigstring, **jsonloadskw )
          
          
          if __name__ == "__main__":  # sanity test
              import sys
              for jsonfile in sys.argv[1:] or ["test.json"]:
                  print( "\n-- " + jsonfile )
                  jsondict = json_load_nocomments( jsonfile )
                      # first few keys, val type --
                  for key, val in list( jsondict.items() )[:10]:
                      n = (len(val) if isinstance( val, (dict, list, str) )
                          else "" )
                      print( "%-10s : %s %s" % (
                              key, type(val).__name__, n ))
          
          

          【讨论】:

            猜你喜欢
            • 2011-03-18
            • 1970-01-01
            • 1970-01-01
            • 2019-02-13
            • 1970-01-01
            • 2011-02-14
            • 1970-01-01
            • 2010-10-07
            相关资源
            最近更新 更多