当前位置: 首页 > news >正文

Go JSON 编码:序列化、反序列化与结构体标签

Go JSON 编码:序列化、反序列化与结构体标签


一、为什么需要 JSON 处理

在现代软件开发中,JSON(JavaScript Object Notation)是应用最广泛的数据交换格式。无论是前后端通信、微服务之间的 RPC 调用、还是配置文件存储,JSON 几乎无处不在。Go 语言在标准库中提供了encoding/json包,让 JSON 的序列化和反序列化变得简单而高效。

Go 处理 JSON 的核心思想是:将 JSON 数据映射到 Go 的结构体上。这是因为 Go 是静态类型语言,我们需要在运行时之前就知道数据的形状(shape)。结构体恰好提供了这种"数据蓝图"。


二、序列化:Go 值 → JSON 字节

2.1 基础 Marshal

json.Marshal函数接受任意 Go 值,返回 JSON 格式的字节切片。其函数签名如下:

funcMarshal(vinterface{})([]byte,error)

最基础的使用姿势是把一个结构体传进去:

packagemainimport("encoding/json""fmt")typeUserstruct{NamestringAgeintEmailstring}funcmain(){u:=User{Name:"张三",Age:28,Email:"zhangsan@example.com",}data,err:=json.Marshal(u)iferr!=nil{fmt.Println("序列化失败:",err)return}fmt.Println(string(data))// 输出: {"Name":"张三","Age":28,"Email":"zhangsan@example.com"}}

注意:默认情况下,JSON 的键名使用的是 Go 结构体的字段名本身(首字母大写),这是 Go 的导出规则决定的——只有导出的字段才能被 JSON 包访问。

2.2 MarshalIndent:可读的 JSON

调试时我们通常希望 JSON 是格式化的。json.MarshalIndentMarshal基础上增加了缩进控制:

data,err:=json.MarshalIndent(u,""," ")// 第二个参数是每一行的前缀// 第三个参数是每一层级的缩进字符串

输出效果:

{"Name":"张三","Age":28,"Email":"zhangsan@example.com"}

2.3 支持的类型映射

Go 值与 JSON 类型之间的映射关系:

Go 类型JSON 类型说明
boolboolean直接映射
float32/float64number所有数字都映射为 float64
int/uint系列number整数也会被 JSON 当作数字
stringstringUTF-8 字符串,默认会转义 HTML 字符
[]bytestringBase64 编码的字符串
nil/空接口 nilnull
slice/arrayarray
mapobjectkey 必须是 string
structobject仅导出字段会被包含
time.TimestringRFC 3339 格式

三、结构体标签(Struct Tags)

结构体标签是 Go 中一种强大的元编程机制。它是写在结构体字段后面的反引号字符串,为字段提供额外的元数据。JSON 包会根据标签来决定序列化和反序列化时的行为。

3.1 基本语法

typeProductstruct{IDint`json:"id"`Namestring`json:"name"`Pricefloat64`json:"price"`Categorystring`json:"category,omitempty"`internalstring// 小写开头,不会被序列化}

标签的格式是json:"键名,选项1,选项2"。主要的选项有:

  • omitempty:当字段为零值时不输出该字段
  • string:将数字或布尔值序列化成 JSON 字符串(带引号)
  • -:完全忽略该字段(即使它是导出的)

3.2 omitempty 的陷阱

omitempty判断"零值"的逻辑和直觉有时不一致:

typeConfigstruct{Portint`json:"port,omitempty"`// 0 时被忽略Timeoutfloat64`json:"timeout,omitempty"`// 0.0 时被忽略Enabledbool`json:"enabled,omitempty"`// false 时被忽略Tags[]string`json:"tags,omitempty"`// nil 或空切片时被忽略// 注意:空结构体{}不会被 omitempty 忽略!}

一个常见的坑:当你有一个bool类型的"是否启用"字段,false是合法值,但omitempty会导致它被省略。解决方法是使用指针类型*bool,nil 才表示"未设置"。

3.3 string 选项

string选项会把数字或布尔值序列化为带引号的字符串。这在对接某些要求严格字符串类型的第三方 API 时很有用:

typeResponsestruct{Codeint`json:"code,string"`// 输出 "code": "200" 而不是 "code": 200Okbool`json:"ok,string"`// 输出 "ok": "true" 而不是 "ok": true}

四、反序列化:JSON 字节 → Go 值

4.1 基础 Unmarshal

json.Unmarshal将 JSON 字节解析到 Go 值中:

funcUnmarshal(data[]byte,vinterface{})error

第二个参数必须是一个指针,因为 Unmarshal 需要修改传入对象的内容:

jsonStr:=`{"name":"李四","age":25,"email":"lisi@example.com"}`typeUserstruct{Namestring`json:"name"`Ageint`json:"age"`Emailstring`json:"email"`}varu User err:=json.Unmarshal([]byte(jsonStr),&u)iferr!=nil{fmt.Println("反序列化失败:",err)return}fmt.Printf("%+v\n",u)// {Name:李四 Age:25 Email:lisi@example.com}

4.2 Unmarshal 的容错能力

Go 的 Unmarshal 比较宽容,它遵循以下原则:

  • 忽略未知字段:JSON 中有但结构体中没有的字段会被静默跳过。这是一把双刃剑——方便但可能掩盖拼写错误。
  • 类型不匹配时静默失败:如果 JSON 中某个字段类型和结构体不匹配,Unmarshal 不会报错,而是返回零值。想严格校验,需要用json.DecoderDisallowUnknownFields()
  • 大小写不敏感:如果同时定义了json:"Name"标签和实际字段名Name,Go 会优先匹配标签名。

4.3 严格反序列化

如果你需要严格的 JSON 反序列化(拒绝未知字段),使用json.Decoder

funcstrictUnmarshal(data[]byte,vinterface{})error{decoder:=json.NewDecoder(bytes.NewReader(data))decoder.DisallowUnknownFields()returndecoder.Decode(v)}

五、嵌套结构与 JSON

现实世界的数据通常是嵌套的。Go 通过嵌套结构体来映射嵌套 JSON:

typeAddressstruct{Citystring`json:"city"`ZipCodestring`json:"zip_code"`}typePersonstruct{Namestring`json:"name"`Ageint`json:"age"`Address Address`json:"address"`Hobbies[]string`json:"hobbies,omitempty"`}funcmain(){p:=Person{Name:"王五",Age:30,Address:Address{City:"北京",ZipCode:"100000",},Hobbies:[]string{"编程","跑步"},}data,_:=json.MarshalIndent(p,""," ")fmt.Println(string(data))}

输出:

{"name":"王五","age":30,"address":{"city":"北京","zip_code":"100000"},"hobbies":["编程","跑步"]}

5.1 匿名嵌入(组合)

Go 的结构体组合特性在 JSON 中也很好用。当匿名嵌入一个结构体时,其字段会被"提升"到父级 JSON 对象中:

typeBaseModelstruct{IDint`json:"id"`CreatedAtstring`json:"created_at"`}typeArticlestruct{BaseModel// 匿名嵌入,字段会被提升Titlestring`json:"title"`Contentstring`json:"content"`}// 序列化后得到: {"id":1,"created_at":"2026-07-10","title":"Go学习","content":"..."}

六、自定义序列化逻辑

6.1 实现 json.Marshaler 和 json.Unmarshaler 接口

当标准行为不满足需求时,可以实现这两个接口来自定义序列化逻辑:

typeMarshalerinterface{MarshalJSON()([]byte,error)}typeUnmarshalerinterface{UnmarshalJSON([]byte)error}

一个实际例子:处理灵活的"可能是字符串也可能是数字"的字段:

typeFlexibleIntintfunc(fi*FlexibleInt)UnmarshalJSON(data[]byte)error{// 先尝试按数字解析varnumintiferr:=json.Unmarshal(data,&num);err==nil{*fi=FlexibleInt(num)returnnil}// 再尝试按字符串解析varstrstringiferr:=json.Unmarshal(data,&str);err==nil{n,err:=strconv.Atoi(str)iferr!=nil{returnfmt.Errorf("无法解析为整数: %s",str)}*fi=FlexibleInt(n)returnnil}returnfmt.Errorf("FlexibleInt: 无法解析 %s",string(data))}typeItemstruct{ID FlexibleInt`json:"id"`Namestring`json:"name"`}

6.2 json.RawMessage:延迟解析

json.RawMessage本质上是一个[]byte别名,它可以暂存一段原始 JSON 数据,等确定结构后再解析:

typeEventstruct{Typestring`json:"type"`Payload json.RawMessage`json:"payload"`// 先原样保存}// 后续根据 Type 的值决定如何解析 Payloadswitchevent.Type{case"user_login":varlogin LoginPayload json.Unmarshal(event.Payload,&login)case"order_create":varorder OrderPayload json.Unmarshal(event.Payload,&order)}

七、流式处理:Encoder 和 Decoder

当处理大量数据或网络流时,使用json.Encoderjson.Decoder比一次性Marshal/Unmarshal更高效——它们直接操作io.Writer/io.Reader,避免了中间大块内存分配。

7.1 Encoder:边写边编码

typeLogEntrystruct{Timestampstring`json:"timestamp"`Levelstring`json:"level"`Messagestring`json:"message"`}// 模拟写入日志文件file,_:=os.Create("app.log")encoder:=json.NewEncoder(file)encoder.SetIndent(""," ")// 可选:格式化输出encoder.Encode(LogEntry{"2026-07-10T12:00:00Z","INFO","服务启动"})encoder.Encode(LogEntry{"2026-07-10T12:01:00Z","ERROR","数据库连接超时"})

7.2 Decoder:边读边解码

// 从 HTTP 请求体中流式读取resp,_:=http.Get("https://api.example.com/users")deferresp.Body.Close()decoder:=json.NewDecoder(resp.Body)// 读取开头的 [token,_:=decoder.Token()// 确保是数组开始ifdelim,ok:=token.(json.Delim);!ok||delim!='['{returnerrors.New("期望 JSON 数组")}// 逐个解码数组中的元素fordecoder.More(){varuser Useriferr:=decoder.Decode(&user);err!=nil{break}fmt.Printf("用户: %+v\n",user)}

八、实际场景练习

练习 1:配置文件解析器

编写一个从 JSON 配置文件读取数据库连接信息的工具:

packagemainimport("encoding/json""fmt""os")typeDBConfigstruct{Hoststring`json:"host"`Portint`json:"port"`Userstring`json:"user"`Passwordstring`json:"password"`// 实际项目中不应该明文存储Databasestring`json:"database"`Poolstruct{MaxOpenConnsint`json:"max_open_conns"`MaxIdleConnsint`json:"max_idle_conns"`}`json:"pool"`}func(d DBConfig)DSN()string{returnfmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4",d.User,d.Password,d.Host,d.Port,d.Database)}funcLoadDBConfig(pathstring)(*DBConfig,error){data,err:=os.ReadFile(path)iferr!=nil{returnnil,fmt.Errorf("读取配置文件失败: %w",err)}varcfg DBConfig decoder:=json.NewDecoder(bytes.NewReader(data))decoder.DisallowUnknownFields()// 不允许出现未知字段,防止拼写错误iferr:=decoder.Decode(&cfg);err!=nil{returnnil,fmt.Errorf("解析配置文件失败: %w",err)}// 校验必填字段ifcfg.Host==""{returnnil,fmt.Errorf("配置项 host 不能为空")}ifcfg.Port==0{returnnil,fmt.Errorf("配置项 port 不能为空")}return&cfg,nil}

练习 2:API 响应包装器

设计一个通用的 API 响应结构,支持任意类型的数据载荷:

packagemainimport("encoding/json""net/http")var(ErrBadRequest=&AppError{Code:400,Message:"请求参数错误"}ErrNotFound=&AppError{Code:404,Message:"资源不存在"}ErrInternal=&AppError{Code:500,Message:"服务器内部错误"})typeAppErrorstruct{Codeint`json:"code"`Messagestring`json:"message"`}func(e*AppError)Error()string{returnfmt.Sprintf("[%d] %s",e.Code,e.Message)}typeAPIResponsestruct{Successbool`json:"success"`Data json.RawMessage`json:"data,omitempty"`Error*AppError`json:"error,omitempty"`}// WriteSuccess 向 http.ResponseWriter 写入成功响应funcWriteSuccess(w http.ResponseWriter,datainterface{})error{dataBytes,err:=json.Marshal(data)iferr!=nil{returnerr}resp:=APIResponse{Success:true,Data:json.RawMessage(dataBytes),}w.Header().Set("Content-Type","application/json; charset=utf-8")w.WriteHeader(http.StatusOK)returnjson.NewEncoder(w).Encode(resp)}// WriteError 向 http.ResponseWriter 写入错误响应funcWriteError(w http.ResponseWriter,errerror){appErr,ok:=err.(*AppError)if!ok{appErr=ErrInternal}resp:=APIResponse{Success:false,Error:appErr,}w.Header().Set("Content-Type","application/json; charset=utf-8")w.WriteHeader(appErr.Code)json.NewEncoder(w).Encode(resp)}

九、关键知识点总结

知识点核心要点
json.Marshal将 Go 值序列化为 JSON 字节。只处理导出字段。
json.Unmarshal将 JSON 字节反序列化为 Go 值。参数必须是指针。默认忽略未知字段和类型不匹配。
结构体标签json:"name,omitempty,string"控制键名、忽略零值、类型包装。
omitempty零值时跳过字段。注意 bool 的 false 也是零值,可能导致数据丢失。
DisallowUnknownFields使用 Decoder 设置,拒绝 JSON 中包含结构体未知的字段,适合严格校验的场景。
自定义序列化实现Marshaler/Unmarshaler接口即可接管单个类型的序列化逻辑。
json.RawMessage字节别名,可以暂存原始 JSON 数据,延迟到后续确定结构后再解析。
Encoder / Decoder流式处理接口,直接操作io.Writer/io.Reader,适合大文件或网络流的场景。

十、踩坑提醒

  1. 未导出的字段不会被序列化:小写开头的字段对 JSON 包不可见。
  2. omitempty + bool = 陷阱:带omitempty的 bool 字段,false值会被吞掉。
  3. 时间格式time.Time默认序列化为 RFC 3339 格式(如"2026-07-10T12:00:00Z"),需要其他格式请自定义MarshalJSON
  4. map 的 key 必须是 string:JSON 对象的键只能是字符串,所以map[int]string无法直接序列化——要么换用map[string]string,要么实现自定义序列化。
  5. []byte 会被 Base64 编码:JSON 不支持原始二进制,[]byte类型会自动做 Base64 编码。
http://www.jsqmd.com/news/1185134/

相关文章:

  • 继承:面试官问“移动机器人和差速机器人怎么设计类”,我这样答
  • 2026年 广东东莞化工纸筒源头工厂推荐榜:支持小批量打样,专业定制与柔性供应实力盘点 - 甄选服务推荐
  • 深度学习模型性能优化:突破准确率瓶颈的完整实战指南
  • 力旷智能:西林瓶自动装盘机控制系统设计技术解析
  • Linux远程部署Oracle 19c:X11转发配置与图形界面报错深度解析
  • 亲身到店探访成都浪琴官方售后服务中心|完整电话与维修地址(2026年7月最新) - 浪琴官方售后服务中心
  • 解决.NET 8.0调用WPS COM组件失败的实战指南
  • Unity网络编程实战:从Socket UDP基础到实时数据同步实现
  • C++图形编程入门:从控制台对角线到图形库绘制的实践指南
  • AI角色一致性控制:基于ControlNet与LoRA的魔法少女变身动画生成实践
  • Android小游戏开发实战:从Canvas绘制到游戏循环设计
  • C++编程入门:从Hello World到规范的项目结构与注释实践
  • 2026最新衢州本地漏水检测公司本地精选权威推荐:正规防水补漏公司优选口碑TOP5:卫生间/厨房/阳台/飘窗/地下室渗漏水维修师傅上门 - 即刻修防水
  • 全能媒体工具深度测评(终章):压轴两大王牌功能!脚本自动化+AI图片风格重塑,解锁全自动创作天花板
  • 智能表单自动化技术:基于AI的保险理赔10步表单自动填写实战
  • 一套 0 成本 Vibe Coding 上线流程:从想法到产品 7 步发布上线
  • C++实现Code 128条形码生成器:从编码原理到BMP图像生成
  • Apache IoTDB时序数据库可靠测评深度拆解
  • 工业信号采集中的抗干扰设计与STM32应用
  • OpenAI硬件设备技术解析:多模态开发与边缘AI应用前瞻
  • 用C语言实现轻量级LLVM IR框架:从内存管理到SSA构建
  • 浪琴中国官方售后服务中心|全新官方地址及客服热线权威信息声明(2026年7月更新) - 浪琴服务中心
  • PGP加密实战指南:从文件加密到磁盘保护,掌握数字隐私主动权
  • 跨境卖家想自建自动化系统?开源方案怎么选
  • Docker Compose编排GitLab:从单机部署到生产级配置
  • UE5动画蓝图实战:Speed混合与循环播放配置详解
  • C++服务器定时器实现:小根堆数据结构与网络连接超时管理详解
  • Qt Designer与YOLO目标检测界面开发实战教程
  • 在 Python 中使用 **headless(无头)模式** 运行 OpenCV,通常指在没有图形界面(如 Linux 服务器、Docker 容器、CI/CD 环境)下运行 OpenCV
  • MyBatis 全方位详解:Java 主流持久层框架实战解析