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
相关产品推荐
相关产品推荐

