本文详解 Go 服务间通过 TCP 传输 JSON 时因意外双重编码(将已序列化的字节数组再次 JSON 编码为字符串)引发的 json: cannot unmarshal string into Go value 错误,并提供完整可运行的修复方案。
本文详解 go 服务间通过 tcp 传输 json 时因意外双重编码(将已序列化的字节数组再次 json 编码为字符串)引发的 `json: cannot unmarshal string into go value` 错误,并提供完整可运行的修复方案。
在 Go 开发中,跨服务传递结构化数据是常见需求,而 JSON 是最常用的序列化格式。但一个看似微小的编码操作失误——双重 JSON 编码——会导致接收方解码时出现经典错误:json: cannot unmarshal string into Go value of type main.NewContainerJSON。该错误的本质是:接收端期望解析一个 JSON 对象(如 {"ContainerName":"myapp",...}),却实际收到了一个被 JSON 字符串化的字节切片(如 "{"ContainerName":"myapp",...}"),即外层多了一对双引号,使整个有效载荷变成一个 JSON 字符串而非 JSON 对象。
问题根源在于第一台服务器(发送端)的以下两行代码:
d, _ := json.Marshal(c) // ✅ 正确:c → []byte(原始 JSON 字节) encoder.Encode(d) // ❌ 错误:将 []byte 当作普通 Go 值再次编码 → JSON 字符串!
json.Encoder.Encode() 方法会将传入的任意 Go 值(包括 []byte)按其类型规则序列化。当传入 []byte 时,encoding/json 默认将其Base64 编码后作为 JSON 字符串输出(参考 Go 文档),结果类似 "eyJDb250YWluZXJOYW1lIjoibXlhcHAiLCJCYXNlU2VydmVyIjoiVWJ1bnR1IDE0LjA0In0=" 或更常见的是直接转义为带引号的字符串(取决于字节内容)。这导致接收端 json.Decoder.Decode(&b) 尝试将一个字符串反序列化到结构体,自然失败。
✅ 正确做法是:直接 Encode 原始结构体 c,而非其 []byte 表示。修改发送端 newUser 函数如下:
func newUser(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
c := newContainerJSON{
ContainerName: r.FormValue("containerName"),
BaseServer: r.FormValue("BaseServer"),
CMS: r.FormValue("CMS"),
WebsiteName: r.FormValue("websiteName"),
DBrootPWD: r.FormValue("dbRootPWD"),
DBadminUname: r.FormValue("dbAdminUname"),
DBadminPWD: r.FormValue("dbAdminPwd"),
}
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
http.Error(w, "Failed to connect to backend", http.StatusInternalServerError)
return
}
defer conn.Close()
encoder := json.NewEncoder(conn)
if err := encoder.Encode(c); err != nil { // ✅ 直接 Encode 结构体
http.Error(w, "Failed to encode JSON", http.StatusInternalServerError)
return
}
}同时,接收端(第二台服务器)需同步修正结构体标签。原代码中所有字段均使用了错误的 json:",string" 标签,该标签会强制将字段值以字符串形式编码/解码(例如将数字 42 编码为 "42"),但此处所有字段本就是字符串类型,且 HTML 表单提交的值天然为字符串,此标签不仅多余,还可能干扰正常解码逻辑。应移除所有 ",string",仅保留标准字段名映射(或显式指定小写 JSON 键以匹配表单字段习惯):
type NewContainerJSON struct {
ContainerName string `json:"containerName"` // 匹配 HTML name="containerName"
BaseServer string `json:"BaseServer"`
CMS string `json:"CMS"`
WebsiteName string `json:"websiteName"`
DBrootPWD string `json:"dbRootPWD"`
DBadminUname string `json:"dbAdminUname"`
DBadminPWD string `json:"dbAdminPwd"`
}⚠️ 注意事项:
- 永远检查 error 返回值:示例中已补充 if err != nil 处理,避免忽略网络或编码错误。
- 资源管理:使用 defer conn.Close() 确保连接及时释放。
- HTTP 响应:Web 处理函数应返回恰当的 HTTP 状态码与消息,而非仅打印日志。
- TCP 服务健壮性:生产环境的 TCP listener 应支持多连接(用 goroutine 处理每个 Accept())、超时控制及优雅关闭。
修复后,数据流变为:
HTML Form → POST → Go Server1 (ParseForm → struct → json.Encode(struct)) → TCP → Go Server2 (json.Decode → struct)
全程保持 JSON 对象层级一致,解码即可成功。
总结:json: cannot unmarshal string into Go value 是 Go JSON 解码中最典型的“类型错配”错误之一,90% 源于发送端意外将 []byte 或其他非结构体类型传给 json.Encoder.Encode()。牢记——Encode 的目标永远是原始 Go 值(struct/map/slice等),而非其 json.Marshal() 的结果。


















