How to parse YAML with dynamic key in Golang

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

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(&params); 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,
},
},
},
}

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:

确定