Python "yaml"模块从 JSON 格式转换意外的 YAML



我试图将JSON数据转换为YAML格式,但得到了意外的YAML输出

使用在线工具将JSON转换为YAML,从而提供预期的YAML输出。但当在下面的Python代码中使用相同的JSON时,会得到意外的不同结果。

import yaml                                                                     
job_template = [                                                                
{                                                                             
"job-template": {                                                           
"name": "{name}_job",                                                     
"description": "job description",                                         
"project-type": "multibranch",                                            
"number-to-keep": 30,                                                     
"days-to-keep": 30,                                                       
"scm": [                                                                  
{                                                                       
"git": {                                                              
"url": "{git_url}"                                                  
}                                                                     
}                                                                       
]                                                                         
}                                                                           
}                                                                             
]                                                                               
yaml.dump(job_template, open("job_template.yaml", "w"))   

预期以下YAML数据:

- job-template:
name: "{name}_job"
description: job description
project-type: multibranch
number-to-keep: 30
days-to-keep: 30
scm:
- git:
url: "{git_url}"

获取以下YAML格式:

- job-template:
days-to-keep: 30
description: job description
name: '{name}_job'
number-to-keep: 30
project-type: multibranch
scm:
- git: {url: '{git_url}'}

使用default_flow_style=False

例如:

import yaml                                                                     
job_template = [                                                                
{                                                                             
"job-template": {                                                           
"name": "{name}_job",                                                     
"description": "job description",                                         
"project-type": "multibranch",                                            
"number-to-keep": 30,                                                     
"days-to-keep": 30,                                                       
"scm": [                                                                  
{                                                                       
"git": {                                                              
"url": "{git_url}"                                                  
}                                                                     
}                                                                       
]                                                                         
}                                                                           
}                                                                             
]                                                                               
yaml.dump(job_template, open("job_template.yaml", "w"), default_flow_style=False)  

问题出在Python代码中:dict是一个无序的容器。pprint只是给出了与您的yaml输出相同的顺序:

>>> pprint.pprint(job_template)
[{'job-template': {'days-to-keep': 30,
'description': 'job description',
'name': '{name}_job',
'number-to-keep': 30,
'project-type': 'multibranch',
'scm': [{'git': {'url': '{git_url}'}}]}}]

如果问题是关于最后一级dict{"url": "{git_url}"}的表示风格,答案由@Rakesh 给出

PyYAML中顺序的更改阻碍了对YAML文件的双向编辑,许多其他解析器已经试图解决这个问题。

一个值得一看的是Ruamel.yaml,它在其概览页面上写道:

block style and key ordering are kept, so you can diff the round-tripped source

作者提供的一个代码示例演示了这一点:

import sys
import ruamel.yaml as yaml
yaml_str = """
3: abc
conf:
10: def
3: gij     # h is missing
more:
- what
- else
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
data['conf'][10] = 'klm'
data['conf'][3] = 'jig'
yaml.dump(data, sys.stdout, Dumper=yaml.RoundTripDumper)
will give you:
3: abc
conf:
10: klm
3: jig       # h is missing
more:
- what
- else

这里将对此进行更充分的讨论。它被描述为PyYAML的替代品,因此应该很容易在您的环境中进行实验。

首先,您应该将作业模板保存在JSON文件中,例如input.json

[                                                                
{                                                                             
"job-template": {                                                           
"name": "{name}_job",                                                     
"description": "job description",                                         
"project-type": "multibranch",                                            
"number-to-keep": 30,                                                     
"days-to-keep": 30,                                                       
"scm": [                                                                  
{                                                                       
"git": {                                                              
"url": "{git_url}"                                                  
}                                                                     
}                                                                       
]                                                                         
}                                                                           
}                                                                             
]

这样,您可以更容易地调整脚本以处理不同的文件。这样做还保证JSON对象中的键是有序的,当您将JSON作为dicts&列表,至少不适用于所有当前版本的Python

因为YAML 1.2(2009年发布的规范)是YAML,您可以只使用一个保存密钥顺序的YAML1.2库加载转储时,将其转换为所需格式。自从PyYAML仍然停留在2005年发布的YAML1.1规范上,您不能使用,但您可以使用ruamel.yaml(免责声明我是该包的作者)。

唯一的"问题"是ruamel.yaml还将保留(流畅)风格。这正是你不想要的。

因此,您必须递归地遍历数据结构并更改包含该信息的属性:

import sys
import ruamel.yaml
def block_style(d):
if isinstance(d, dict):
d.fa.set_block_style()
for key, value in d. items():
try:
if '{' in value:
d[key] = ruamel.yaml.scalarstring.DoubleQuotedScalarString(value)
except TypeError:
pass
block_style(value)
elif isinstance(d, list):
d.fa.set_block_style()
for elem in d:
block_style(elem)
yaml = ruamel.yaml.YAML()
with open('input.json') as fp:
data = yaml.load(fp)
block_style(data)
yaml.dump(data, sys.stdout)

它给出:

- job-template:
name: "{name}_job"
description: job description
project-type: multibranch
number-to-keep: 30
days-to-keep: 30
scm:
- git:
url: "{git_url}"

以上内容同样适用于Python 2和Python 3

'{'的额外代码测试是在不能表示为纯标量的字符串周围强制使用双引号。默认情况下,如果不需要YAML双引号标量中可用的额外转义序列来表示字符串,ruamel.yaml将使用单引号标量。

最新更新