英文:
How to parse YAML with dynamic key in Golang
问题
我正在尝试使用Go解析一个YAML文件。问题是YAML文件中的键可能并不总是相同的。这是为了进行API版本控制,以便用户可以定义他们支持的版本。例如V1、V2、V3等。它们不需要按顺序排列,并且可以省略他们不支持的版本,例如V0、V2、V5等。
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
var data = `
---
development:
skip-header-validation: true
V1:
current: "1.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
skip-mime-type-validation: true
skip-version-validation: true
V2:
current: "2.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
`
type MajorVersion struct {
Current string `yaml:"current"`
MimeTypes []string `yaml:"mime_types"`
SkipVersionValidation bool `yaml:"skip-version-validation"`
SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
}
type Environment struct {
SkipHeaderValidation bool `yaml:"skip-header-validation"`
Version map[string]MajorVersion
}
func main() {
e := Environment{}
yaml.Unmarshal([]byte(data), &e)
fmt.Println(e)
}
我在这里看到了一个类似的问题:https://stackoverflow.com/questions/18412126/golang-parse-a-json-with-dynamic-key
这是在顶层,我还没有完全弄清楚如何从结构体内部实现这个。
英文:
I am trying to parse a YAML file with Go. The problem is that the keys in the YAML file might not always be the same. This is to do API versioning so the user can define the versions they support. For instance V1, V2, V3 etc. They do not need to be in order and can omit versions they don't support i.e, V0, V2, V5 etc.
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
var data = `
---
development:
skip-header-validation: true
V1:
current: "1.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
skip-mime-type-validation: true
skip-version-validation: true
V2:
current: "2.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
`
type MajorVersion struct {
Current string `yaml:"current"`
MimeTypes []string `yaml:"mime_types"`
SkipVersionValidation bool `yaml:"skip-version-validation"`
SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
}
type Environment struct {
SkipHeaderValidation bool `yaml:"skip-header-validation"`
Version map[string]MajorVersion
}
func main() {
e := Environment{}
yaml.Unmarshal([]byte(data), &e)
fmt.Println(e)
}
I saw a similar question asked here
This is at the top level and I haven't quite figured out how to do this from inside the struct.
答案1
得分: 20
首先,你试图将根解析为Environment
,但实际类型是map[string]Environment
。其次,如果你想保持该类型结构,你需要一个自定义的Unmarshaler
。可以像这样实现:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
var data = `
---
development:
skip-header-validation: true
V1:
current: "1.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
skip-mime-type-validation: true
skip-version-validation: true
V2:
current: "2.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
`
type MajorVersion struct {
Current string `yaml:"current"`
MimeTypes []string `yaml:"mime_types"`
SkipVersionValidation bool `yaml:"skip-version-validation"`
SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
}
type Environment struct {
SkipHeaderValidation bool
Versions map[string]MajorVersion
}
func (e *Environment) UnmarshalYAML(unmarshal func(interface{}) error) error {
var params struct {
SkipHeaderValidation bool `yaml:"skip-header-validation"`
}
if err := unmarshal(¶ms); err != nil {
return err
}
var versions map[string]MajorVersion
if err := unmarshal(&versions); err != nil {
// 这里我们期望出现错误,因为布尔值无法转换为 MajorVersion
if _, ok := err.(*yaml.TypeError); !ok {
return err
}
}
e.SkipHeaderValidation = params.SkipHeaderValidation
e.Versions = versions
return nil
}
func main() {
var e map[string]Environment
if err := yaml.Unmarshal([]byte(data), &e); err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%#v\n", e)
}
输出结果(使用 makeup 后):
map[string]main.Environment{
"development": {
SkipHeaderValidation: true,
Versions: {
"V2": {
Current: "2.0.0",
MimeTypes: {"application/vnd.company.jk.identity+json;", "application/vnd.company.jk.user+json;", "application/vnd.company.jk.role+json;", "application/vnd.company.jk.scope+json;", "application/vnd.company.jk.test+json;"},
SkipVersionValidation: false,
SkipMimeTypeValidation: false,
},
"V1": {
Current: "1.0.0",
MimeTypes: {"application/vnd.company.jk.identity+json;", "application/vnd.company.jk.user+json;", "application/vnd.company.jk.role+json;", "application/vnd.company.jk.scope+json;", "application/vnd.company.jk.test+json;"},
SkipVersionValidation: true,
SkipMimeTypeValidation: true,
},
},
},
}
英文:
First, you are trying to parse the root as an Environment
, but its actual type is map[string]Environment
. Second, you are going to need a custom Unmarshaler
if you want to keep that type structure. Something like this:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
var data = `
---
development:
skip-header-validation: true
V1:
current: "1.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
skip-mime-type-validation: true
skip-version-validation: true
V2:
current: "2.0.0"
mime_types:
- application/vnd.company.jk.identity+json;
- application/vnd.company.jk.user+json;
- application/vnd.company.jk.role+json;
- application/vnd.company.jk.scope+json;
- application/vnd.company.jk.test+json;
`
type MajorVersion struct {
Current string `yaml:"current"`
MimeTypes []string `yaml:"mime_types"`
SkipVersionValidation bool `yaml:"skip-version-validation"`
SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
}
type Environment struct {
SkipHeaderValidation bool
Versions map[string]MajorVersion
}
func (e *Environment) UnmarshalYAML(unmarshal func(interface{}) error) error {
var params struct {
SkipHeaderValidation bool `yaml:"skip-header-validation"`
}
if err := unmarshal(&params); err != nil {
return err
}
var versions map[string]MajorVersion
if err := unmarshal(&versions); err != nil {
// Here we expect an error because a boolean cannot be converted to a
// a MajorVersion
if _, ok := err.(*yaml.TypeError); !ok {
return err
}
}
e.SkipHeaderValidation = params.SkipHeaderValidation
e.Versions = versions
return nil
}
func main() {
var e map[string]Environment
if err := yaml.Unmarshal([]byte(data), &e); err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%#v\n", e)
}
Output (after using makeup):
map[string]main.Environment{
"development": {
SkipHeaderValidation: true,
Versions: {
"V2": {
Current: "2.0.0",
MimeTypes: {"application/vnd.company.jk.identity+json;", "application/vnd.company.jk.user+json;", "application/vnd.company.jk.role+json;", "application/vnd.company.jk.scope+json;", "application/vnd.company.jk.test+json;"},
SkipVersionValidation: false,
SkipMimeTypeValidation: false,
},
"V1": {
Current: "1.0.0",
MimeTypes: {"application/vnd.company.jk.identity+json;", "application/vnd.company.jk.user+json;", "application/vnd.company.jk.role+json;", "application/vnd.company.jk.scope+json;", "application/vnd.company.jk.test+json;"},
SkipVersionValidation: true,
SkipMimeTypeValidation: true,
},
},
},
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论