How to parse YAML with dynamic key in Golang

huangapple go评论135阅读模式
英文:

How to parse YAML with dynamic key in Golang

问题

我正在尝试使用Go解析一个YAML文件。问题是YAML文件中的键可能并不总是相同的。这是为了进行API版本控制,以便用户可以定义他们支持的版本。例如V1、V2、V3等。它们不需要按顺序排列,并且可以省略他们不支持的版本,例如V0、V2、V5等。

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/yaml.v2"
  5. )
  6. var data = `
  7. ---
  8. development:
  9. skip-header-validation: true
  10. V1:
  11. current: "1.0.0"
  12. mime_types:
  13. - application/vnd.company.jk.identity+json;
  14. - application/vnd.company.jk.user+json;
  15. - application/vnd.company.jk.role+json;
  16. - application/vnd.company.jk.scope+json;
  17. - application/vnd.company.jk.test+json;
  18. skip-mime-type-validation: true
  19. skip-version-validation: true
  20. V2:
  21. current: "2.0.0"
  22. mime_types:
  23. - application/vnd.company.jk.identity+json;
  24. - application/vnd.company.jk.user+json;
  25. - application/vnd.company.jk.role+json;
  26. - application/vnd.company.jk.scope+json;
  27. - application/vnd.company.jk.test+json;
  28. `
  29. type MajorVersion struct {
  30. Current string `yaml:"current"`
  31. MimeTypes []string `yaml:"mime_types"`
  32. SkipVersionValidation bool `yaml:"skip-version-validation"`
  33. SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
  34. }
  35. type Environment struct {
  36. SkipHeaderValidation bool `yaml:"skip-header-validation"`
  37. Version map[string]MajorVersion
  38. }
  39. func main() {
  40. e := Environment{}
  41. yaml.Unmarshal([]byte(data), &e)
  42. fmt.Println(e)
  43. }

我在这里看到了一个类似的问题: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.

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/yaml.v2"
  5. )
  6. var data = `
  7. ---
  8. development:
  9. skip-header-validation: true
  10. V1:
  11. current: "1.0.0"
  12. mime_types:
  13. - application/vnd.company.jk.identity+json;
  14. - application/vnd.company.jk.user+json;
  15. - application/vnd.company.jk.role+json;
  16. - application/vnd.company.jk.scope+json;
  17. - application/vnd.company.jk.test+json;
  18. skip-mime-type-validation: true
  19. skip-version-validation: true
  20. V2:
  21. current: "2.0.0"
  22. mime_types:
  23. - application/vnd.company.jk.identity+json;
  24. - application/vnd.company.jk.user+json;
  25. - application/vnd.company.jk.role+json;
  26. - application/vnd.company.jk.scope+json;
  27. - application/vnd.company.jk.test+json;
  28. `
  29. type MajorVersion struct {
  30. Current string `yaml:"current"`
  31. MimeTypes []string `yaml:"mime_types"`
  32. SkipVersionValidation bool `yaml:"skip-version-validation"`
  33. SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
  34. }
  35. type Environment struct {
  36. SkipHeaderValidation bool `yaml:"skip-header-validation"`
  37. Version map[string]MajorVersion
  38. }
  39. func main() {
  40. e := Environment{}
  41. yaml.Unmarshal([]byte(data), &e)
  42. fmt.Println(e)
  43. }

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。可以像这样实现:

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/yaml.v2"
  5. )
  6. var data = `
  7. ---
  8. development:
  9. skip-header-validation: true
  10. V1:
  11. current: "1.0.0"
  12. mime_types:
  13. - application/vnd.company.jk.identity+json;
  14. - application/vnd.company.jk.user+json;
  15. - application/vnd.company.jk.role+json;
  16. - application/vnd.company.jk.scope+json;
  17. - application/vnd.company.jk.test+json;
  18. skip-mime-type-validation: true
  19. skip-version-validation: true
  20. V2:
  21. current: "2.0.0"
  22. mime_types:
  23. - application/vnd.company.jk.identity+json;
  24. - application/vnd.company.jk.user+json;
  25. - application/vnd.company.jk.role+json;
  26. - application/vnd.company.jk.scope+json;
  27. - application/vnd.company.jk.test+json;
  28. `
  29. type MajorVersion struct {
  30. Current string `yaml:"current"`
  31. MimeTypes []string `yaml:"mime_types"`
  32. SkipVersionValidation bool `yaml:"skip-version-validation"`
  33. SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
  34. }
  35. type Environment struct {
  36. SkipHeaderValidation bool
  37. Versions map[string]MajorVersion
  38. }
  39. func (e *Environment) UnmarshalYAML(unmarshal func(interface{}) error) error {
  40. var params struct {
  41. SkipHeaderValidation bool `yaml:"skip-header-validation"`
  42. }
  43. if err := unmarshal(&params); err != nil {
  44. return err
  45. }
  46. var versions map[string]MajorVersion
  47. if err := unmarshal(&versions); err != nil {
  48. // 这里我们期望出现错误,因为布尔值无法转换为 MajorVersion
  49. if _, ok := err.(*yaml.TypeError); !ok {
  50. return err
  51. }
  52. }
  53. e.SkipHeaderValidation = params.SkipHeaderValidation
  54. e.Versions = versions
  55. return nil
  56. }
  57. func main() {
  58. var e map[string]Environment
  59. if err := yaml.Unmarshal([]byte(data), &e); err != nil {
  60. fmt.Println(err.Error())
  61. }
  62. fmt.Printf("%#v\n", e)
  63. }

输出结果(使用 makeup 后):

  1. map[string]main.Environment{
  2. "development": {
  3. SkipHeaderValidation: true,
  4. Versions: {
  5. "V2": {
  6. Current: "2.0.0",
  7. 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;"},
  8. SkipVersionValidation: false,
  9. SkipMimeTypeValidation: false,
  10. },
  11. "V1": {
  12. Current: "1.0.0",
  13. 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;"},
  14. SkipVersionValidation: true,
  15. SkipMimeTypeValidation: true,
  16. },
  17. },
  18. },
  19. }
英文:

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:

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/yaml.v2"
  5. )
  6. var data = `
  7. ---
  8. development:
  9. skip-header-validation: true
  10. V1:
  11. current: "1.0.0"
  12. mime_types:
  13. - application/vnd.company.jk.identity+json;
  14. - application/vnd.company.jk.user+json;
  15. - application/vnd.company.jk.role+json;
  16. - application/vnd.company.jk.scope+json;
  17. - application/vnd.company.jk.test+json;
  18. skip-mime-type-validation: true
  19. skip-version-validation: true
  20. V2:
  21. current: "2.0.0"
  22. mime_types:
  23. - application/vnd.company.jk.identity+json;
  24. - application/vnd.company.jk.user+json;
  25. - application/vnd.company.jk.role+json;
  26. - application/vnd.company.jk.scope+json;
  27. - application/vnd.company.jk.test+json;
  28. `
  29. type MajorVersion struct {
  30. Current string `yaml:"current"`
  31. MimeTypes []string `yaml:"mime_types"`
  32. SkipVersionValidation bool `yaml:"skip-version-validation"`
  33. SkipMimeTypeValidation bool `yaml:"skip-mime-type-validation"`
  34. }
  35. type Environment struct {
  36. SkipHeaderValidation bool
  37. Versions map[string]MajorVersion
  38. }
  39. func (e *Environment) UnmarshalYAML(unmarshal func(interface{}) error) error {
  40. var params struct {
  41. SkipHeaderValidation bool `yaml:"skip-header-validation"`
  42. }
  43. if err := unmarshal(&params); err != nil {
  44. return err
  45. }
  46. var versions map[string]MajorVersion
  47. if err := unmarshal(&versions); err != nil {
  48. // Here we expect an error because a boolean cannot be converted to a
  49. // a MajorVersion
  50. if _, ok := err.(*yaml.TypeError); !ok {
  51. return err
  52. }
  53. }
  54. e.SkipHeaderValidation = params.SkipHeaderValidation
  55. e.Versions = versions
  56. return nil
  57. }
  58. func main() {
  59. var e map[string]Environment
  60. if err := yaml.Unmarshal([]byte(data), &e); err != nil {
  61. fmt.Println(err.Error())
  62. }
  63. fmt.Printf("%#v\n", e)
  64. }

Output (after using makeup):

  1. map[string]main.Environment{
  2. "development": {
  3. SkipHeaderValidation: true,
  4. Versions: {
  5. "V2": {
  6. Current: "2.0.0",
  7. 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;"},
  8. SkipVersionValidation: false,
  9. SkipMimeTypeValidation: false,
  10. },
  11. "V1": {
  12. Current: "1.0.0",
  13. 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;"},
  14. SkipVersionValidation: true,
  15. SkipMimeTypeValidation: true,
  16. },
  17. },
  18. },
  19. }

huangapple
  • 本文由 发表于 2015年8月22日 02:41:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/32147325.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定