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

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

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

问题

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

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

package main

import (
	"encoding/json"

	types "github.com/compose-spec/compose-go/types"
)

func main() {
	out := types.Project{
		Name: "foo",
		Services: []types.ServiceConfig{ // <- 注意这个字段是一个数组
			{
				Name:  "bar",
				Image: "hello-world",
			},
		},
	}

	buf, err := json.Marshal(out)
	if err != nil {
		panic(err)
	}

	println(string(buf)) // <- 注意Services字段现在是一个映射,这是错误的!

	var in types.Project
	if err := json.Unmarshal(buf, &in); err != nil {
		panic(err)
	}
}

代码运行失败:

{"name":"foo","services":{"bar":{"command":null,"entrypoint":null,"image":"hello-world"}}}
panic: json: cannot unmarshal object into Go struct field Project.services of type types.Services

goroutine 1 [running]:
main.main()
	/tmp/sandbox1081064727/prog.go:29 +0x168

out对象被序列化为:

{
    "name": "foo",
    "services": {
        "bar": {
            "command": null,
            "entrypoint": null,
            "image": "hello-world"
        }
    }
}

实际上应该是这样的:

{
    "name": "foo",
    "services": [
        {
            "name": "bar",
            "command": null,
            "entrypoint": null,
            "image": "hello-world"
        }
    ]
}
英文:

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.

package main

import (
	&quot;encoding/json&quot;

	types &quot;github.com/compose-spec/compose-go/types&quot;
)

func main() {
	out := types.Project{
		Name: &quot;foo&quot;,
		Services: []types.ServiceConfig{ // &lt;- notice this field is an array
			{
				Name:  &quot;bar&quot;,
				Image: &quot;hello-world&quot;,
			},
		},
	}

	buf, err := json.Marshal(out)
	if err != nil {
		panic(err)
	}

	println(string(buf)) // &lt;- notice the Services field now a map, which is incorrect!

	var in types.Project
	if err := json.Unmarshal(buf, &amp;in); err != nil {
		panic(err)
	}
}

The code fails to run:

{&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;}}}
panic: json: cannot unmarshal object into Go struct field Project.services of type types.Services

goroutine 1 [running]:
main.main()
	/tmp/sandbox1081064727/prog.go:29 +0x168

The out object is serialized as

{
    &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;
        }
    }
}

which should really be something like

{
    &quot;name&quot;: &quot;foo&quot;,
    &quot;services&quot;: [
        {
            &quot;name&quot;: &quot;bar&quot;,
            &quot;command&quot;: null,
            &quot;entrypoint&quot;: null,
            &quot;image&quot;: &quot;hello-world&quot;
        }
    ]
}

答案1

得分: 2

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

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

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

// MarshalYAML使Services实现yaml.Marshaller
func (s Services) MarshalYAML() (interface{}, error) {
    services := map[string]ServiceConfig{}
    for _, service := range s {
        services[service.Name] = service
    }
    return services, nil
}

// MarshalJSON使Services实现json.Marshaler
func (s Services) MarshalJSON() ([]byte, error) {
    data, err := s.MarshalYAML()
    if err != nil {
        return nil, err
    }
    return json.MarshalIndent(data, "", "  ")
}
英文:

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:

// MarshalYAML makes Services implement yaml.Marshaller
func (s Services) MarshalYAML() (interface{}, error) {
	services := map[string]ServiceConfig{}
	for _, service := range s {
		services[service.Name] = service
	}
	return services, nil
}

// MarshalJSON makes Services implement json.Marshaler
func (s Services) MarshalJSON() ([]byte, error) {
	data, err := s.MarshalYAML()
	if err != nil {
		return nil, err
	}
	return json.MarshalIndent(data, &quot;&quot;, &quot;  &quot;)
}

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:

确定