`json.Marshal()`将结构体的数组字段转换为映射,这是不应该的。

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

`json.Marshal()` converts an array field of a struct to map which it shouldn't

问题

我想将来自github.com/compose-spectypes.Project对象序列化为JSON。

然而,这个结构体中应该是一个数组的字段总是被序列化为一个映射。

  1. package main
  2. import (
  3. "encoding/json"
  4. types "github.com/compose-spec/compose-go/types"
  5. )
  6. func main() {
  7. out := types.Project{
  8. Name: "foo",
  9. Services: []types.ServiceConfig{ // <- 注意这个字段是一个数组
  10. {
  11. Name: "bar",
  12. Image: "hello-world",
  13. },
  14. },
  15. }
  16. buf, err := json.Marshal(out)
  17. if err != nil {
  18. panic(err)
  19. }
  20. println(string(buf)) // <- 注意Services字段现在是一个映射,这是错误的!
  21. var in types.Project
  22. if err := json.Unmarshal(buf, &in); err != nil {
  23. panic(err)
  24. }
  25. }

代码运行失败:

  1. {"name":"foo","services":{"bar":{"command":null,"entrypoint":null,"image":"hello-world"}}}
  2. panic: json: cannot unmarshal object into Go struct field Project.services of type types.Services
  3. goroutine 1 [running]:
  4. main.main()
  5. /tmp/sandbox1081064727/prog.go:29 +0x168

out对象被序列化为:

  1. {
  2. "name": "foo",
  3. "services": {
  4. "bar": {
  5. "command": null,
  6. "entrypoint": null,
  7. "image": "hello-world"
  8. }
  9. }
  10. }

实际上应该是这样的:

  1. {
  2. "name": "foo",
  3. "services": [
  4. {
  5. "name": "bar",
  6. "command": null,
  7. "entrypoint": null,
  8. "image": "hello-world"
  9. }
  10. ]
  11. }
英文:

I want to serialize object of types.Project from github.com/compose-spec into JSON

However, a field of ths struct which is supposed to be array, is always serialized into map.

  1. package main
  2. import (
  3. &quot;encoding/json&quot;
  4. types &quot;github.com/compose-spec/compose-go/types&quot;
  5. )
  6. func main() {
  7. out := types.Project{
  8. Name: &quot;foo&quot;,
  9. Services: []types.ServiceConfig{ // &lt;- notice this field is an array
  10. {
  11. Name: &quot;bar&quot;,
  12. Image: &quot;hello-world&quot;,
  13. },
  14. },
  15. }
  16. buf, err := json.Marshal(out)
  17. if err != nil {
  18. panic(err)
  19. }
  20. println(string(buf)) // &lt;- notice the Services field now a map, which is incorrect!
  21. var in types.Project
  22. if err := json.Unmarshal(buf, &amp;in); err != nil {
  23. panic(err)
  24. }
  25. }

The code fails to run:

  1. {&quot;name&quot;:&quot;foo&quot;,&quot;services&quot;:{&quot;bar&quot;:{&quot;command&quot;:null,&quot;entrypoint&quot;:null,&quot;image&quot;:&quot;hello-world&quot;}}}
  2. panic: json: cannot unmarshal object into Go struct field Project.services of type types.Services
  3. goroutine 1 [running]:
  4. main.main()
  5. /tmp/sandbox1081064727/prog.go:29 +0x168

The out object is serialized as

  1. {
  2. &quot;name&quot;: &quot;foo&quot;,
  3. &quot;services&quot;: {
  4. &quot;bar&quot;: {
  5. &quot;command&quot;: null,
  6. &quot;entrypoint&quot;: null,
  7. &quot;image&quot;: &quot;hello-world&quot;
  8. }
  9. }
  10. }

which should really be something like

  1. {
  2. &quot;name&quot;: &quot;foo&quot;,
  3. &quot;services&quot;: [
  4. {
  5. &quot;name&quot;: &quot;bar&quot;,
  6. &quot;command&quot;: null,
  7. &quot;entrypoint&quot;: null,
  8. &quot;image&quot;: &quot;hello-world&quot;
  9. }
  10. ]
  11. }

答案1

得分: 2

你看到的行为是正确的,符合compose文件规范,规范中指出:

Compose文件必须声明一个services根元素,其键是服务名称的字符串表示,值是服务定义。

将列表转换为映射是通过compose-go/types.go中的自定义编组器实现的:

  1. // MarshalYAML使Services实现yaml.Marshaller
  2. func (s Services) MarshalYAML() (interface{}, error) {
  3. services := map[string]ServiceConfig{}
  4. for _, service := range s {
  5. services[service.Name] = service
  6. }
  7. return services, nil
  8. }
  9. // MarshalJSON使Services实现json.Marshaler
  10. func (s Services) MarshalJSON() ([]byte, error) {
  11. data, err := s.MarshalYAML()
  12. if err != nil {
  13. return nil, err
  14. }
  15. return json.MarshalIndent(data, "", " ")
  16. }
英文:

The behavior you're seeing is correct with respect to the compose file specification, which says:

> A Compose file MUST declare a services root element as a map whose keys are string representations of service names, and whose values are service definitions.

The transformation of the list to a map is implemented by the custom marshalers in compose-go/types.go:

  1. // MarshalYAML makes Services implement yaml.Marshaller
  2. func (s Services) MarshalYAML() (interface{}, error) {
  3. services := map[string]ServiceConfig{}
  4. for _, service := range s {
  5. services[service.Name] = service
  6. }
  7. return services, nil
  8. }
  9. // MarshalJSON makes Services implement json.Marshaler
  10. func (s Services) MarshalJSON() ([]byte, error) {
  11. data, err := s.MarshalYAML()
  12. if err != nil {
  13. return nil, err
  14. }
  15. return json.MarshalIndent(data, &quot;&quot;, &quot; &quot;)
  16. }

huangapple
  • 本文由 发表于 2023年2月16日 12:27:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467868.html
匿名

发表评论

匿名网友

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

确定