Go 对 yaml 的读写没有标准库支持,第三方库中 go-yaml/yaml 的支持比较好。
go-yaml/yaml 的使用非常简单,下面举例说明。
yaml 文档
比如有以下的 yaml 文档:
table: filter
enabled: [mon,osd]
disabled: []
ipv4:
mon:
priority: 100
tcp:
- "-p tcp -m multiport --dport 6789,3300 -m state --state NEW -j ACCEPT"
- "-p tcp -m multiport --dport 6789,3300 -m state --state ESTABLISHED -j ACCEPT"
osd:
priority: 200
tcp:
- "-p tcp -m multiport --dport 6800:7300 -m state --state NEW -j ACCEPT"
- "-p tcp -m multiport --dport 6800:7300 -m state --state ESTABLISHED -j ACCEPT"
Go 数据结构
我们想要的 Go 数据结构如下:
type Config struct {
Table *string `yaml:"table,omitempty"`
Enabled []string `yaml:"enabled,flow,omitempty"`
Disabled []string `yaml:"disabled,flow,omitempty"`
RulesV4 map[string]Rule `yaml:"ipv4,omitempty"`
RulesV6 map[string]Rule `yaml:"ipv6,omitempty"`
}
type Rule struct {
Priority *int `yaml:"priority,omitempty"`
TCP []string `yaml:"tcp,omitempty"`
UDP []string `yaml:"udp,omitempty"`
}
反序列化
如下的代码可以非常容易的从前面的 yaml 文档中解析出想要的数据结构:
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (raw *Config) UnmarshalYAML(node *yaml.Node) error {
defaultTable := "filter"
*raw = Config{
Table: &defaultTable,
}
type plain Config
if err := node.Decode((*plain)(raw)); err != nil {
return errors.Wrapf(err, "yaml: failed to decode document: %s", err)
}
return nil
}
// Parse parses a config fragment in YAML format.
func Parse(data []byte) (raw Config, err error) {
decoder := yaml.NewDecoder(bytes.NewBuffer(data))
decoder.KnownFields(true)
if err = decoder.Decode(&raw); err != nil {
return Config{}, errors.Wrap(err, "config: failed to parse config file")
}
return
}
自定义 yaml.Unmarshaler
接口是可选的,可以在解析 yaml 文档时实现一些基本的校验,或者也可以记录当前解析的数据结构在文档节点中定义的行列位置,用于辅助错误提示等:
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a YAML document.
type Unmarshaler interface {
UnmarshalYAML(value *Node) error
}
序列化
至于序列化为 yaml 文档,也很简单:
//
// write to stdout
//
// https://github.com/go-yaml/yaml/issues/166
// https://github.com/go-yaml/yaml/pull/455
var buffer bytes.Buffer
encoder := yaml.NewEncoder(&buffer)
encoder.SetIndent(2)
err = encoder.Encode(raw)
if err != nil {
return errors.Wrapf(err, "failed to encode config: %s", err)
}
_, err = os.Stdout.Write(buffer.Bytes())
同样的,可以通过类似上面自定义 yaml.Unmarshaler
的例子自定义 yaml.Marshaler
接口:
// The Marshaler interface may be implemented by types to customize their
// behavior when being marshaled into a YAML document. The returned value
// is marshaled in place of the original value implementing Marshaler.
//
// If an error is returned by MarshalYAML, the marshaling procedure stops
// and returns with the provided error.
type Marshaler interface {
MarshalYAML() (interface{}, error)
}
注意事项
并不是只有结构体才能自定义 yaml.Unmarshaler
和 yaml.Marshaler
接口,只需要将这些字段的类型使用 type
关键字定义成用户自定义类型,然后就可以为这些自定义类型实现 yaml.Unmarshaler
或 yaml.Marshaler
接口。
上面的反序列化和序列化分别用到了 yaml.Decoder
和 yaml.Encoder
,如果不需要特殊设置参数,也可以直接使用 yaml.Unmarshal
和 yaml.Marshal
接口。
需要注意的是,前面提到由于 Go 的基础类型没有 nil 值,所以 Go 数据结构中的基础类型要定义成指针类型,否则 yaml 文档中的 0 值与文档结构中字段未定义等价,从而导致不管加或者不加 omitempty
都存在歧义:
-
不
omitempty
时,文档结构中未定义字段在反序列化时被错误的设置成 0 值; -
当
omitempty
时,文档结构中未定义字段在反序列化时被错误的设置成 0 值,同时 0 值在序列化成文档结构时被忽略变成未定义字段;
参考资料
A tour of YAML parsers for Go
http://sweetohm.net/article/go-yaml-parsers.en.html
最后修改于 2019-05-10