有没有一种方法可以确定一个文件是以YAML还是JSON格式存储的?

7
我有一个Python测试脚本,需要一个配置文件。该配置文件应该是JSON格式的。
但我的一些测试脚本用户不喜欢JSON格式,因为它难以阅读。
所以我更改了我的测试脚本,让它期望配置文件为YAML格式,然后将YAML文件转换为JSON文件。
我希望加载配置文件的函数能够处理JSON和YAML两种格式。在yaml或json模块中是否有一种方法可以给出布尔响应来判断配置文件是JSON还是YAML?
我现在的解决方法是使用两个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)

现在,我在互联网上找到了一个名为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)

3
YAML不是JSON的超集吗?你应该能够无条件地将文件加载为YAML。(我不确定它是否是精确的超集 - 我认为之前的版本不是。) - user2357112
1
你不能让YAML文件使用一种扩展名,JSON文件使用另一种吗? - user2357112
user2357112,有两个问题。1)一些用户可能会在配置文件中使用没有.yml或.json后缀的名称,因此我不能通过后缀来确定他们的配置文件。2)仅仅因为一个文件有.yml后缀并不意味着该文件一定是YAML格式的。 - SQA777
user2357112,我使用yaml.load测试了加载json文件的功能,并使用json.load测试了加载yaml文件的功能,两者都通过了断言(这是在try/except块之外)。 - SQA777
2
不要使用 PyYAML 的 load() 函数来处理不受控制的数据,这是不安全的(例如,可能会导致磁盘被清除)。 - Anthon
@user2357112 这仅适用于YAML 1.2版本,而OP使用的是PyYAML(从import yaml可推断出),它不支持YAML 1.2版本,只支持1.1。 - Anthon
4个回答

8
从您的
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,也不需要针对任一格式进行测试。


4
从查看jsonyaml模块的文档来看,它们似乎没有提供任何适当的模块。然而,一个常见的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_jsonis_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所提到的那样,"每个JSON文件也是一个有效的YAML文件"(从YAML 1.2开始),因此您应该能够无条件地将所有内容都处理为YAML(假设您有一个兼容YAML 1.2的解析器;Python默认的 yaml 模块不兼容)。


你最后的陈述并不适用,因为OP的“import yaml”是指PyYAML,它仅支持较旧的YAML 1.1规范。 - Anthon
在编程中,不指定异常就使用tryexcept是一种不好的做法。在处理JSON时要捕获的异常是**ValueError**。而yaml模块甚至不会引发异常。 - Ricardo Branco
UnicodeDecodeError是UnicodeError的子类,而UnicodeError本身则是ValueError的子类。例如,当您的脚本捕获Ctrl-C(ExceptionError)时,您不希望load_config()函数中的except块处理该异常。 - Ricardo Branco
我尝试使用 with open("/etc/passwd") as f: d = yaml.load_safe(f),但没有触发任何异常。无论如何,我会使用 yaml.scanner.ScannerError 作为捕获异常的方法。 - Ricardo Branco
感谢 @RicardoBranco。我将在 except 子句中指定异常。 - SQA777
显示剩余2条评论

0

我不知道这个问题是否已经得到解答,但是这里有一种方法可以实现

def input_parameters(file):
default_ext = '.json' #set a default extension
file_ext = pathlib.Path(file).suffix
with open(file, 'r') as f:
    if file_ext == default_ext:
        input_file = json.load(f)
    else:
        input_file = yaml.safe_load(f)
return input_file

0
多年以后,我遇到了同样的问题。我完全赞同EAFP,但我仍然在尝试找到最佳方法来检测配置文件是否为JSON格式或YAML格式。 在代码中,我有一些方法可以告知用户他在json文件中出了哪个问题,在YAML文件中又在哪里。try/except 并没有像我想的那样处理它,当我看到那些嵌套块时,我的眼睛都要瞎了。
这不是完美的,仍然有小问题,但基本概念适合我的需求。我会说“够用了”。
我的解决方案是:找到配置文件中所有可能的独立逗号。如果配置文件包含独立逗号(json中的分隔符),那么它就是json文件;如果我们找不到任何逗号,那么它就是yaml文件。 在我的yaml文件中,我仅在注释(在“”之间)和列表(在[]之间)中使用逗号。 也许有人会觉得这很有用。
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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接