本文记录了本人在做yaml解析时踩的坑,谨以此纪念我在这几个小时里挠掉的头发。
情景再现
昨天发现之前改的一个yaml文件解析出了问题:我需要从一个yaml配置文件里读取内容,然后解析到一个struct中。但实际运行时发现,有一部分参数没有解析出来,是空的。 看看代码(注:以下都是把我的实际问题做了抽象和简化之后的代码):
我的yaml配置文件:
kind: PersonalInfo
name: he
age: 18
复制代码
接收配置的结构体定义:
type Config struct {
TypeMeta `json:",inline"`
Name string `json:"name"`
Words string `json:"words"`
}
type TypeMeta struct {
Kind string `json:"kind,omitempty"`
}
复制代码
刚开始我是直接用了go-yaml包,这样做的解析:
func TestParseYaml(t *testing.T) {
ymlFilePath := "conf.yaml"
ymlFile, err := os.Open(ymlFilePath)
if err != nil {
t.Errorf("open yaml file(%s) failed: %v", ymlFilePath, err)
return
}
defer ymlFile.Close()
ymlContent, err := ioutil.ReadAll(ymlFile)
if err != nil {
t.Errorf("read yaml file(%s) failed: %v", ymlFilePath, err)
return
}
var config Config
err = yaml.Unmarshal(ymlContent, &config)
if err != nil {
t.Errorf("yaml format error: %v", err)
return
}
t.Logf("%#v", config)
}
复制代码
运行输出结果为:
{TypeMeta:{Kind:} Name:he Age:18}
复制代码
看着这个解析结果,我不禁陷入了沉思——我的kind呢???是谁吃掉了我的kind???
原因分析
我尝试修改了我的struct,再测试了几遍,发现没有解析出来的都是在匿名变量中的数据。
但是我的struct去解析json字符串的话,所有字段,包括匿名变量中的字段,全部都能正常解析。
到底是什么影响了我解析yaml格式的数据呢?
找了一堆资料进行了一通分析,终于发现了华点——struct的标签一直被我忽略掉了!
我的struct里面,都只加了json的标签,并没有任何yaml相关的标签,所以在进行yaml转换的时候,相当于所有字段都没有打标签。匿名成员如果没有加inline
标签,在解析时是会被忽略的。
修改验证
基于以上分析,我修改了我的struct类型定义,给匿名变量加上了yaml的inline标签:
type Config struct {
TypeMeta `json:",inline" yaml:",inline"`
Name string `json:"name"`
Age int `json:"age"`
}
type TypeMeta struct {
Kind string `json:"kind,omitempty"`
}
复制代码
再次执行我的解析函数,得到的结果如下:
{TypeMeta:{Kind:PersonalInfo} Name:he Age:18}
复制代码
泪目,我终于拿到了正确的结果!
最终方案
虽然在struct上添加yaml标签就可以得到想要的输出,但是看看我们的代码,struct上加的标签几乎全都是json的,大家并不会单独给yaml加标签。 如果我单独给我用到的这个struct加yaml标签,好像有点不符合我们一贯的风格。而且大家定义struct时几乎都不会给变量加yaml标签,下次要是别的谁又加了个匿名成员,却忘记加yaml标签,岂不就凉凉了?
所以,经过一番权衡,我决定先把从配置文件读取到的yaml内容转换成json,然后再使用json的unmarshal方法,把json转换为struct。
最终解析yaml的方法如下:
type Config struct {
TypeMeta `json:",inline"`
Name string `json:"name"`
Age int `json:"age"`
}
type TypeMeta struct {
Kind string `json:"kind,omitempty"`
}
func TestParseYaml(t *testing.T) {
ymlFilePath := "conf.yaml"
ymlFile, err := os.Open(ymlFilePath)
if err != nil {
t.Errorf("open yaml file(%s) failed: %v", ymlFilePath, err)
return
}
defer ymlFile.Close()
ymlContent, err := ioutil.ReadAll(ymlFile)
if err != nil {
t.Errorf("read yaml file(%s) failed: %v", ymlFilePath, err)
return
}
data, err := yaml.ToJSON(ymlContent)
var config Config
err = json.Unmarshal(data, &config)
if err != nil {
t.Errorf("yaml format error: %v", err)
return
}
t.Logf("%+v", config)
}
复制代码
输出结果:
{TypeMeta:{Kind:PersonalInfo} Name:he Age:18}复制代码
有疑问加站长微信联系(非本文作者)