
本文详解 go 中通过 `marshaljson()` 方法自定义结构体 json 序列化的方式,指出常见错误(如手动拼接导致无效 json),并提供安全、标准的实现方案,同时对比推荐使用结构体标签的简洁做法。
在 Go 中,当需要精细控制结构体的 JSON 输出格式时,可实现 json.Marshaler 接口(即定义 MarshalJSON() ([]byte, error) 方法)。但直接字符串拼接极易生成非法 JSON——如原代码中仅写入 "foo""true",缺失字段名、引号不匹配、布尔值被错误加引号等,导致解析器报错 invalid character 'o' in literal false。
✅ 正确实现 MarshalJSON():手动生成合规 JSON
必须严格遵循 JSON 语法:对象用 {} 包裹,字段名和字符串值需双引号包裹,布尔值(true/false)不可加引号,字段间用逗号分隔。以下是修正后的完整示例:
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
type Info struct {
name string // 注意:小写字段默认不导出,无法被 json 包访问
flag bool
}
// MarshalJSON 实现:生成标准 JSON 对象
func (i Info) MarshalJSON() ([]byte, error) {
var b bytes.Buffer
b.Write([]byte(`{"name":"`)) // 字段名 + 开始引号
b.WriteString(i.name) // 安全写入字符串(自动转义)
b.Write([]byte(`","flag":`)) // 字段分隔 + 布尔字段名
if i.flag {
b.Write([]byte("true")) // 布尔字面量,不加引号
} else {
b.Write([]byte("false"))
}
b.Write([]byte(`}`)) // 结束大括号
return b.Bytes(), nil
}
func main() {
a := []Info{
{"foo", true},
{"bar", false},
}
out, err := json.Marshal(a)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
// 输出: [{"name":"foo","flag":true},{"name":"bar","flag":false}]
}⚠️ 关键注意事项:使用 b.WriteString() 替代 b.Write([]byte(...)) 可避免手动处理字符串转义(如 name 含 " 或 \n 时);小写字段(如 name)在结构体中为非导出字段,json 包默认忽略;若坚持用小写字段,需在 MarshalJSON 中显式处理其值(如本例所示);布尔值 true/false 必须以 JSON 原生字面量形式输出,加引号会使其变为字符串,破坏类型一致性(反序列化时无法还原为 bool)。
? 更推荐:使用导出字段 + JSON 标签(零成本定制)
绝大多数场景下,无需手动实现 MarshalJSON。只需将字段首字母大写(使其可导出),再配合 json struct tag 即可灵活控制键名与行为:
type Info struct {
Name string `json:"name"` // JSON 键名为 "name"
Flag bool `json:"flag"` // JSON 键名为 "flag"
// 可选:omitempty 忽略零值字段
// Age int `json:"age,omitempty"`
}此时调用 json.Marshal() 会自动处理字段映射、转义、类型序列化,且完全兼容 json.Unmarshal()。示例输出与手动实现一致:
自定义设置的程度更高可以满足大部分中小型企业的建站需求,同时修正了上一版中发现的BUG,优化了核心的代码占用的服务器资源更少,执行速度比上一版更快 主要的特色功能如下: 1)特色的菜单设置功能,菜单设置分为顶部菜单和底部菜单,每一项都可以进行更名、选择是否隐 藏,排序等。 2)增加企业基本信息设置功能,输入的企业信息可以在网页底部的醒目位置看到。 3)增加了在线编辑功能,输入产品信息,企业介绍等栏
[{"name":"foo","flag":true},{"name":"bar","flag":false}]此外,json tag 还支持更多高级选项:
- ,omitempty:当字段为零值(空字符串、0、nil 等)时跳过该字段;
- -,string:将数值字段序列化为字符串(如 Age intjson:"age,string"→"age":"25"`);
- -:完全忽略该字段。
✅ 总结:何时选择哪种方式?
| 方式 | 适用场景 | 维护性 | 安全性 |
|---|---|---|---|
| 导出字段 + struct tag | 字段名映射、零值忽略、数值字符串化等常规定制 | 高(声明式,无逻辑) | 最高(由标准库保障) |
| 自定义 MarshalJSON | 需动态计算字段值、嵌套结构重排、加密/脱敏、兼容旧协议等复杂逻辑 | 中低(需手动维护 JSON 语法) | 依赖开发者严谨性 |
? 最佳实践建议:优先使用 json struct tag;仅当业务逻辑无法通过标签表达时,再谨慎实现 MarshalJSON,并务必通过 json.Valid() 或单元测试验证输出合法性。









