ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Go encoding-json序列化进阶:从零值陷阱到自定义Marshal

Go encoding-json序列化进阶:从零值陷阱到自定义Marshal Go encoding-json序列化进阶从零值陷阱到自定义Marshal文章导语Go的encoding/json包是API开发中使用最频繁的标准库之一。但它的反直觉行为——omitempty对零值的处理、time.Time的默认格式、interface{}反序列化的类型推断——常常成为线上Bug的温床。本文从实战出发梳理JSON序列化的高阶用法和避坑大全。一、结构体Tag的完整规范typeUserstruct{// 1. 基本用法Namestringjson:name// 2. omitempty零值时忽略Emailstringjson:email,omitempty// 3. string数值序列化为字符串Ageintjson:age,string// 4. -忽略该字段Passwordstringjson:-// 5. 嵌套/扁平化Inner Innerjson:,inline}omitempty的零值陷阱typeOrderstruct{IDintjson:id,omitempty// 0会被省略Amountfloat64json:amount,omitempty// 0.0会被省略Statusstringjson:status,omitempty// 会被省略Paidbooljson:paid,omitempty// false会被省略Time time.Timejson:time,omitempty// 零值时间会被省略}order:Order{}json.Marshal(order)// {} ——所有字段都被忽略了解决方案——使用指针类型typeOrderstruct{ID*intjson:id,omitempty// nil时省略Amount*float64json:amount,omitempty// nil时省略Paid*booljson:paid,omitempty// nil时省略}二、自定义序列化/反序列化2.1 实现Marshaler/Unmarshaler接口typeStatusintconst(StatusPending StatusiotaStatusActive StatusInactive)varstatusToStringmap[Status]string{StatusPending:pending,StatusActive:active,StatusInactive:inactive,}varstringToStatusmap[string]Status{pending:StatusPending,active:StatusActive,inactive:StatusInactive,}func(s Status)MarshalJSON()([]byte,error){returnjson.Marshal(statusToString[s])}func(s*Status)UnmarshalJSON(data[]byte)error{varstrstringiferr:json.Unmarshal(data,str);err!nil{returnerr}val,ok:stringToStatus[str]if!ok{returnfmt.Errorf(未知状态: %s,str)}*svalreturnnil}2.2 time.Time的自定义格式typeCustomTimestruct{time.Time}constcustomFormat2006-01-02 15:04:05func(ct CustomTime)MarshalJSON()([]byte,error){returnjson.Marshal(ct.Format(customFormat))}func(ct*CustomTime)UnmarshalJSON(data[]byte)error{varsstringiferr:json.Unmarshal(data,s);err!nil{returnerr}t,err:time.Parse(customFormat,s)iferr!nil{returnerr}ct.Timetreturnnil}三、性能优化3.1 json.Encoder vs json.Marshal// 对于大量数据使用Encoder流式写入varbuf bytes.Buffer encoder:json.NewEncoder(buf)for_,item:rangeitems{encoder.Encode(item)// 逐条写入}// 而json.Marshal需要一次性分配所有内存data,_:json.Marshal(items)3.2 使用json.RawMessage延迟解析typeEventstruct{Typestringjson:typePayload json.RawMessagejson:payload// 延迟解析}funchandleEvent(data[]byte)error{varevent Eventiferr:json.Unmarshal(data,event);err!nil{returnerr}switchevent.Type{caseuser_created:varuser User json.Unmarshal(event.Payload,user)caseorder_placed:varorder Order json.Unmarshal(event.Payload,order)}returnnil}3.3 sync.Pool复用EncodervarencoderPoolsync.Pool{New:func()interface{}{returnjson.NewEncoder(nil)},}funcencode(vinterface{})([]byte,error){varbuf bytes.Buffer enc:json.NewEncoder(buf)enc.Encode(v)returnbuf.Bytes(),nil}四、实战API请求/响应的统一序列化// 统一API响应结构typeAPIResponsestruct{Codeintjson:codeMessagestringjson:messageDatainterface{}json:data,omitempty}// null vs 空数组的处理funcSafeSlice[T any](slice[]T)interface{}{ifslicenil{return[]T{}// 返回空数组而非null}returnslice}func(r APIResponse)WithData(datainterface{})APIResponse{// 自动处理nil切片ifv:reflect.ValueOf(data);v.Kind()reflect.Slicev.IsNil(){datareflect.MakeSlice(v.Type(),0,0).Interface()}returnAPIResponse{Code:r.Code,Message:r.Message,Data:data}}五、全文总结omitempty对零值类型0, “”, false都会触发省略用指针避免自定义MarshalJSON/UnmarshalJSON处理自定义类型json.RawMessage延迟解析节省性能和内存Encoder流式输出优于Marshal整批nil切片序列化为null用make初始化返回[]六、技术进阶展望sonic/bytedance的高性能JSON库对比json.NewDecoder的流式解析大文件Go泛型在JSON处理中的应用参考文献Go encoding/json包文档: https://pkg.go.dev/encoding/jsonGo Blog - JSON and Go《Go语言圣经》- JSONDave Cheney - json.Decoder vs json.Unmarshal字节跳动sonic库: https://github.com/bytedance/sonic
返回列表