【问题标题】:Is there a way to determine whether a file is in YAML or JSON format?有没有办法确定文件是 YAML 还是 JSON 格式?
【发布时间】:2017-11-04 10:51:41
【问题描述】:

我有一个需要配置文件的 Python 测试脚本。配置文件应为 JSON 格式。

但是我的测试脚本的一些用户不喜欢 JSON 格式,因为它不可读。

所以我更改了我的测试脚本,使其需要 YAML 格式的配置文件,然后将 YAML 文件转换为 JSON 文件。

我希望加载配置文件的函数同时处理 JSON 和 YAML。如果配置文件是 JSON 或 YAML,yaml 或 json 模块中是否有方法可以给我一个布尔响应?

我现在的解决方法是使用两个 try/except 子句:

import os
import json
import yaml

# This is the configuration file - my script gets it from argparser but in
# this example, let's just say it is some file that I don't know what the format
# is
config_file = "some_config_file"

in_fh = open(config_file, "r")

config_dict = dict()
valid_json = True
valid_yaml = True

try:
    config_dict = json.load(in_fh)
except:
    print "Error trying to load the config file in JSON format"
    valid_json = False

try:
    config_dict = yaml.load(in_fh)
except:
    print "Error trying to load the config file in YAML format"
    valid_yaml = False

in_fh.close()

if not valid_yaml and not valid_json:
    print "The config file is neither JSON or YAML"
    sys.exit(1)

现在,我在 Internet 上找到了一个名为 isityaml 的 Python 模块,可用于测试 YAML。但我不想安装另一个包,因为我必须在多个测试主机上安装它。

json 和 yaml 模块是否有一种方法可以返回一个布尔值来测试它们各自的格式?

config_file = "sample_config_file"

# I would like some method like this
if json.is_json(in_fh):
    config_dict = json.load(in_fh)

【问题讨论】:

  • YAML 不是 JSON 的超集吗?您应该能够无条件地将文件加载为 YAML。 (我不确定它是否是一个精确的超集——我认为以前的版本不是。)
  • 你不能只要求 YAML 文件有一个扩展名而 JSON 文件有一个不同的扩展名吗?
  • user2357112,有两个问题。 1) 一些用户可能会在没有 .yml 或 .json 后缀的情况下命名他们的配置文件,所以我不能在他们的配置文件中使用后缀 2) 仅仅因为文件具有 .yml 后缀并不一定意味着该文件是YAML 格式。
  • user2357112,我测试了使用 yaml.load 加载 json 文件和使用 json.load 加载 yaml 文件,并且都断言(这是在 try/except 块之外)
  • 不要使用 PyYAML 的load(),在不受控制的数据上,它是不安全的(即你可以擦除你的光盘)。

标签: python json yaml


【解决方案1】:

来自你的

import yaml

我断定您使用的是旧的 PyYAML。该包仅支持 YAML 1.1(从 2005 年开始),并且指定的格式不是 JSON 的完整超集。随着 YAML 1.2(2009 年发布),YAML 格式成为 JSON 的超集。

包ruamel.yaml(免责声明:我是该包的作者)支持 YAML 1.2。您可以使用pip install ruamel.yaml 将其安装在您的python 虚拟环境中。通过将 PyYAML 替换为 ruamel.yaml(而不是添加包),您可以这样做:

import os
from ruamel.yaml import YAML

config_file = "some_config_file"

yaml = YAML()
with open(config_file, "r") as in_fh:
    config_dict = yaml.load(in_fh)

并将文件加载到config_dict,不关心输入是YAML还是JSON,也不需要对任何一种格式进行测试。

【讨论】:

    【解决方案2】:

    从查看 json 和 yaml 模块的文档来看,它们似乎没有提供任何合适的模块。然而,一个常见的 Python 习语是EAFP(“请求宽恕比请求许可更容易”);换句话说,继续尝试执行操作,并在出现异常时处理。

    def load_config(config_file):
        with open(config_file, "r") as in_fh:
            # Read the file into memory as a string so that we can try
            # parsing it twice without seeking back to the beginning and
            # re-reading.
            config = in_fh.read()
    
        config_dict = dict()
        valid_json = True
        valid_yaml = True
    
        try:
            config_dict = json.loads(config)
        except:
            print "Error trying to load the config file in JSON format"
            valid_json = False
    
        try:
            config_dict = yaml.safe_load(config)
        except:
            print "Error trying to load the config file in YAML format"
            valid_yaml = False
    

    如果需要,您可以创建自己的 is_json 或 is_yaml 函数。这将涉及两次处理配置,但这可能适合您的目的。

    def try_as(loader, s, on_error):
        try:
            loader(s)
            return True
        except on_error:
            return False
    
    def is_json(s):
        return try_as(json.loads, s, ValueError)
    
    def is_yaml(s):
        return try_as(yaml.safe_load, s, yaml.scanner.ScannerError)
    

    最后,正如@user2357112 提到的,"every JSON file is also a valid YAML file"(从 YAML 1.2 开始),所以您应该能够无条件地将所有内容作为 YAML 处理(假设您有一个与 YAML 1.2 兼容的解析器;Python 的默认 yaml 模块是't)。

    【讨论】:

    • 您的最后一句话并不真正适用,因为 OP import yaml 指的是 PyYAML,它只支持旧的 YAML 1.1 规范。
    • 在不指定异常的情况下执行try 和except 是不好的做法。在 JSON 的情况下要捕获的异常是 `ValueError。 yaml 模块甚至不会引发异常。
    • UnicodeDecodeError 是 UnicodeError 的子类,它本身是 ValueError 的子类。例如,当您的脚本捕获 Ctrl-C (ExceptionError) 时,您不希望 load_config() 函数中的 except 块来处理它。
    • 我试过with open("/etc/passwd") as f: d = yaml.load_safe(f),没有触发异常。无论如何,我会使用 yaml.scanner.ScannerError 作为异常来捕获。
    • @RicardoBranco - /etc/passwd 被解析为单个 YAML 字符串。 yaml.safe_load('a: b: c') 会抛出异常。
    【解决方案3】:

    多年后,我遇到了同样的麻烦。我完全同意 EAFP,但如果配置文件是 JSON 格式或 YAML,我仍在尝试找到最佳检测。 在代码中,我有方法通知用户 where 他确实在 json 文件中发出了问题以及在 YAML 中的位置。 try/except 没有按我的意愿处理这个问题,当我看到那些嵌套块时,我的眼睛在流血。

    这并不完美,还有一些小问题,但对我来说,基本概念符合我的需要。我会说“足够好”。

    我的解决方案是:在配置文件中找到所有可能的独立逗号。如果配置文件包含独立的逗号(json 中的分隔符)我们有 json-file,如果我们没有找到任何逗号,它是 yaml。 在我的 yaml 文件中,我只在 cmets(“”之间)和列表([] 之间)中使用逗号。 也许有人会觉得它有用。

    import re
    from pathlib import Path
    
    commas = re.compile(r',(?=(?![\"]*[\s\w\?\.\"\!\-\_]*,))(?=(?![^\[]*\]))')
    """
    Find all commas which are standalone 
     - not between quotes - comments, answers
     - not between brackets - lists
    """
    file_path = Path("example_file.cfg")
    signs = commas.findall(file_path.open('r').read())
    
    return "json" if len(signs) > 0 else "yaml"
    

    【讨论】:

      猜你喜欢
      • 2021-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      相关资源
      最近更新 更多