You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

json.Marshal()将结构体数组字段错误序列化为map的问题

问题分析

github.com/compose-spec/compose-go/types里的Services类型,底层是切片,但它实现了自定义JSON序列化逻辑:序列化时会把切片转换成以服务名称为Key的Map输出,这就是你看到services字段变成对象而非数组的原因。而反序列化时,该类型的逻辑只接受数组格式,所以序列化后的Map无法被反序列化回去,直接报错。

解决方案

方案1:自定义兼容结构体序列化

如果需要把Project序列化为数组格式的services,可以定义一个和types.Project结构对齐,但services字段用原生切片的结构体,转换后再序列化:

package main

import (
	"encoding/json"
	"fmt"

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

// 自定义结构体,对齐types.Project的结构,但services用原生切片
type CustomProject struct {
	Name     string              `json:"name"`
	Services []types.ServiceConfig `json:"services"`
	// 其他需要的字段,直接从types.Project复制对应的标签和类型即可
}

func main() {
	out := types.Project{
		Name: "foo",
		Services: []types.ServiceConfig{
			{
				Name:  "bar",
				Image: "hello-world",
			},
		},
	}

	// 转换为自定义结构体
	customProj := CustomProject{
		Name:     out.Name,
		Services: out.Services,
	}

	buf, err := json.MarshalIndent(customProj, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))

	// 反序列化测试
	var in CustomProject
	if err := json.Unmarshal(buf, &in); err != nil {
		panic(err)
	}
	// 需要转回types.Project的话,再做一次类型转换
	var originalProj types.Project
	originalProj.Name = in.Name
	originalProj.Services = in.Services
	fmt.Println("反序列化后的项目名称:", originalProj.Name)
}

方案2:单独处理Services字段序列化

不想定义新结构体的话,可以单独处理services字段,绕开它的自定义序列化逻辑:

package main

import (
	"encoding/json"
	"fmt"

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

func main() {
	out := types.Project{
		Name: "foo",
		Services: []types.ServiceConfig{
			{
				Name:  "bar",
				Image: "hello-world",
			},
		},
	}

	// 构建临时Map存放字段
	projData := map[string]interface{}{
		"name": out.Name,
		// 其他字段按需添加
	}

	// 单独把services序列化为数组格式
	servicesBuf, err := json.Marshal(out.Services)
	if err != nil {
		panic(err)
	}
	projData["services"] = json.RawMessage(servicesBuf)

	// 序列化整个Map
	buf, err := json.MarshalIndent(projData, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))
}
补充说明

你初始化时能直接用切片给Services字段赋值,是因为types.Services的底层类型就是[]types.ServiceConfig,但它的自定义MarshalJSON方法改写了序列化行为,导致输出变成Map格式。

内容的提问来源于stack exchange,提问作者wxh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 00:04:02