为什么在golang中,YAML.v3不根据String()方法来序列化结构体?

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

Why doesn't YAML.v3 marshal a struct based on the String() in golang?

问题

我有一个包含基于枚举的类型的结构体。我想将其渲染为用户友好的字符串。以下是最小可行代码:

  1. package main
  2. import (
  3. "fmt"
  4. "gopkg.in/yaml.v3"
  5. )
  6. type Job struct {
  7. Engine Engine `json:"Engine" yaml:"Engine"`
  8. }
  9. //go:generate stringer -type=Engine --trimprefix=Engine
  10. type Engine int
  11. const (
  12. engineUnknown Engine = iota // 必须是第一个
  13. EngineDocker
  14. engineDone // 必须是最后一个
  15. )
  16. func main() {
  17. j := Job{Engine: EngineDocker}
  18. fmt.Printf("%+v\n\n", j)
  19. out, _ := yaml.Marshal(j)
  20. fmt.Println(string(out))
  21. }

这是生成的代码:

  1. // Code generated by "stringer -type=Engine --trimprefix=Engine"; DO NOT EDIT.
  2. package main
  3. import "strconv"
  4. func _() {
  5. // "invalid array index" 编译错误表示常量值已更改。
  6. // 重新运行 stringer 命令以再次生成它们。
  7. var x [1]struct{}
  8. _ = x[engineUnknown-0]
  9. _ = x[EngineDocker-1]
  10. _ = x[engineDone-2]
  11. }
  12. const _Engine_name = "engineUnknownDockerengineDone"
  13. var _Engine_index = [...]uint8{0, 13, 19, 29}
  14. func (i Engine) String() string {
  15. if i < 0 || i >= Engine(len(_Engine_index)-1) {
  16. return "Engine(" + strconv.FormatInt(int64(i), 10) + ")"
  17. }
  18. return _Engine_name[_Engine_index[i]:_Engine_index[i+1]]
  19. }

这是输出:

  1. {Engine:1}
  2. Engine: 1

这是我希望输出的内容:

  1. {Engine:Docker}
  2. Engine: Docker

我以为生成的文件中的String()方法可以实现这个。有没有办法做到这一点?谢谢!

英文:

I have a struct which contains a type based on an enum. I am trying to render it to a user friendly string. Here's minimum viable code:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;gopkg.in/yaml.v3&quot;
  5. )
  6. type Job struct {
  7. Engine Engine `json:&quot;Engine&quot; yaml:&quot;Engine&quot;`
  8. }
  9. //go:generate stringer -type=Engine --trimprefix=Engine
  10. type Engine int
  11. const (
  12. engineUnknown Engine = iota // must be first
  13. EngineDocker
  14. engineDone // must be last
  15. )
  16. func main() {
  17. j := Job{Engine: EngineDocker}
  18. fmt.Printf(&quot;%+v\n\n&quot;, j)
  19. out, _ := yaml.Marshal(j)
  20. fmt.Println(string(out))
  21. }

Here's the generated code:

  1. // Code generated by &quot;stringer -type=Engine --trimprefix=Engine&quot;; DO NOT EDIT.
  2. package main
  3. import &quot;strconv&quot;
  4. func _() {
  5. // An &quot;invalid array index&quot; compiler error signifies that the constant values have changed.
  6. // Re-run the stringer command to generate them again.
  7. var x [1]struct{}
  8. _ = x[engineUnknown-0]
  9. _ = x[EngineDocker-1]
  10. _ = x[engineDone-2]
  11. }
  12. const _Engine_name = &quot;engineUnknownDockerengineDone&quot;
  13. var _Engine_index = [...]uint8{0, 13, 19, 29}
  14. func (i Engine) String() string {
  15. if i &lt; 0 || i &gt;= Engine(len(_Engine_index)-1) {
  16. return &quot;Engine(&quot; + strconv.FormatInt(int64(i), 10) + &quot;)&quot;
  17. }
  18. return _Engine_name[_Engine_index[i]:_Engine_index[i+1]]
  19. }

Here's the output:

  1. {Engine:1}
  2. Engine: 1

Here's what I'd like the output to be:

  1. {Engine:Docker}
  2. Engine: Docker

I thought the String() in the generated file would accomplish this. Is there any way to do this? Thanks!

答案1

得分: 1

yaml编组器不使用String方法。相反,YAML使用encoding.TextMarshalerencoding.TextUnmarshaler接口。实际上,所有其他编解码方案 - JSON、XML、TOML等 - 都使用这些接口来读取/写入值。因此,如果您为自己的类型实现这些方法,您将免费获得所有其他编解码器。

这是一个示例,演示如何为枚举类型创建可读性强的编码:https://go.dev/play/p/pEcBmAM-oZJ

  1. type Engine int
  2. const (
  3. engineUnknown Engine = iota // 必须是第一个
  4. EngineDocker
  5. engineDone // 必须是最后一个
  6. )
  7. var engineNames []string
  8. var engineNameToValue map[string]Engine
  9. func init() {
  10. engineNames = []string{"Unknown", "Docker"}
  11. engineNameToValue = make(map[string]Engine)
  12. for i, name := range engineNames {
  13. engineNameToValue[strings.ToLower(name)] = Engine(i)
  14. }
  15. }
  16. func (e Engine) String() string {
  17. if e < 0 || int(e) >= len(engineNames) {
  18. panic(fmt.Errorf("Invalid engine code: %d", e))
  19. }
  20. return engineNames[e]
  21. }
  22. func ParseEngine(text string) (Engine, error) {
  23. i, ok := engineNameToValue[strings.ToLower(text)]
  24. if !ok {
  25. return engineUnknown, fmt.Errorf("Invalid engine name: %s", text)
  26. }
  27. return i, nil
  28. }
  29. func (e Engine) MarshalText() ([]byte, error) {
  30. return []byte(e.String()), nil
  31. }
  32. func (e *Engine) UnmarshalText(text []byte) (err error) {
  33. name := string(text)
  34. *e, err = ParseEngine(name)
  35. return
  36. }

它的工作原理:

  1. func main() {
  2. j := Job{Engine: EngineDocker}
  3. fmt.Printf("%#v\n\n", j)
  4. out, err := yaml.Marshal(j)
  5. if err != nil {
  6. panic(err)
  7. }
  8. fmt.Printf("YAML: %s\n", string(out))
  9. var jj Job
  10. err = yaml.Unmarshal(out, &jj)
  11. if err != nil {
  12. panic(err)
  13. }
  14. fmt.Printf("%#v\n\n", jj)
  15. // == JSON ==
  16. out, err = json.Marshal(j)
  17. if err != nil {
  18. panic(err)
  19. }
  20. fmt.Printf("JSON: %s\n", string(out))
  21. var jjs Job
  22. err = json.Unmarshal(out, &jjs)
  23. if err != nil {
  24. panic(err)
  25. }
  26. fmt.Printf("%#v\n\n", jjs)
  27. }

输出结果:

  1. main.Job{Engine:1}
  2. YAML: Engine: Docker
  3. main.Job{Engine:1}
  4. JSON: {"Engine":"Docker"}
  5. main.Job{Engine:1}

看到了吗?它在YAML和JSON中都以字符串形式写入和读取,而无需任何额外的努力。

英文:

yaml marshaler doesn't use String method. Instead YAML uses encoding.TextMarshaler and encoding.TextUnmarshaler interfaces. Actually, all other codec schemes - JSON, XML, TOML, etc. - use those interfaces to read/write the values. So, if you implement those methods for your type, you will receive all other codecs for free.

Here is an example how to make a human-readable encoding for your enum: https://go.dev/play/p/pEcBmAM-oZJ

  1. type Engine int
  2. const (
  3. engineUnknown Engine = iota // must be first
  4. EngineDocker
  5. engineDone // must be last
  6. )
  7. var engineNames []string
  8. var engineNameToValue map[string]Engine
  9. func init() {
  10. engineNames = []string{&quot;Unknown&quot;, &quot;Docker&quot;}
  11. engineNameToValue = make(map[string]Engine)
  12. for i, name := range engineNames {
  13. engineNameToValue[strings.ToLower(name)] = Engine(i)
  14. }
  15. }
  16. func (e Engine) String() string {
  17. if e &lt; 0 || int(e) &gt;= len(engineNames) {
  18. panic(fmt.Errorf(&quot;Invalid engine code: %d&quot;, e))
  19. }
  20. return engineNames[e]
  21. }
  22. func ParseEngine(text string) (Engine, error) {
  23. i, ok := engineNameToValue[strings.ToLower(text)]
  24. if !ok {
  25. return engineUnknown, fmt.Errorf(&quot;Invalid engine name: %s&quot;, text)
  26. }
  27. return i, nil
  28. }
  29. func (e Engine) MarshalText() ([]byte, error) {
  30. return []byte(e.String()), nil
  31. }
  32. func (e *Engine) UnmarshalText(text []byte) (err error) {
  33. name := string(text)
  34. *e, err = ParseEngine(name)
  35. return
  36. }

How it works:

  1. func main() {
  2. j := Job{Engine: EngineDocker}
  3. fmt.Printf(&quot;%#v\n\n&quot;, j)
  4. out, err := yaml.Marshal(j)
  5. if err != nil {
  6. panic(err)
  7. }
  8. fmt.Printf(&quot;YAML: %s\n&quot;, string(out))
  9. var jj Job
  10. err = yaml.Unmarshal(out, &amp;jj)
  11. if err != nil {
  12. panic(err)
  13. }
  14. fmt.Printf(&quot;%#v\n\n&quot;, jj)
  15. // == JSON ==
  16. out, err = json.Marshal(j)
  17. if err != nil {
  18. panic(err)
  19. }
  20. fmt.Printf(&quot;JSON: %s\n&quot;, string(out))
  21. var jjs Job
  22. err = json.Unmarshal(out, &amp;jjs)
  23. if err != nil {
  24. panic(err)
  25. }
  26. fmt.Printf(&quot;%#v\n\n&quot;, jjs)
  27. }

the output

  1. main.Job{Engine:1}
  2. YAML: Engine: Docker
  3. main.Job{Engine:1}
  4. JSON: {&quot;Engine&quot;:&quot;Docker&quot;}
  5. main.Job{Engine:1}

See? It writes and reads strings to both YAML and JSON without any extra effort.

huangapple
  • 本文由 发表于 2022年9月28日 08:31:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/73875048.html
匿名

发表评论

匿名网友

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

确定