Golang中Protobuf Token结构体JSON反序列化字段异常问题
Go中Protobuf Token结构体JSON反序列化异常修复
问题背景
给定HTTP返回的JSON响应:
{"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZCRXFDSnFER21hMUxLZUtoZUVxcSJ9.eyJpc3MiOiJodHRwczovL2Rldi0xY3p5M3Vpa21pNjJ0dWN4LnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw0OTAiLCJhdWQiOiJhdXRoLXNlcnZpY2Ui...", "scope": "read:sample", "expires_in": 86400, "token_type": "Bearer"}
对应的Protobuf Token 定义:
message Token { // jwt access token string access_token = 1; // scope of the token string scope = 2; // expiration time int32 expires_in = 3; // token type string token_type = 4; }
通过protoc-gen-go生成的Go结构体如下:
type Token struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // jwt access token AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` // scope of the token Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` // expiration time ExpiresIn int32 `protobuf:"varint,3,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` // token type TokenType string `protobuf:"bytes,4,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` }
尝试使用encoding/json的json.NewDecoder(resp.Body).Decode(&token),以及jsonpb.Unmarshal(resp.Body, token)方法后,反序列化出现异常:转为JSON输出时,expires_in字段值乱码,token_type内容被合并到expires_in且自身为空,输出结果:
{"token": {"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZCRXFDSnFER21hMUxLZUtoZUVxcSJ9.eyJpc3MiOiJodHRwczovL2Rldi0xY3p5M3Vpa21pNjJ0dWN4LnV...", "scope": "read:sample", "expires_in": "\"\u0006Bearer", "token_type": ""}}
问题根源
- 响应体重复读取未重置:HTTP响应体
resp.Body是流式读取对象,第一次读取后指针会移至末尾,后续读取会从末尾开始,导致解析时数据错位,出现字段值混乱。 - jsonpb使用错误:调用
jsonpb.Unmarshal时未传入结构体指针,导致无法正确写入解析后的数据。
修复方案
方案1:预读取响应体到内存,避免重复读取
将响应体一次性读取到字节数组中,后续所有反序列化操作基于该数组,彻底解决流式读取的问题:
import ( "encoding/json" "io" "bytes" "github.com/golang/protobuf/jsonpb" ) // 读取响应体全部内容 bodyBytes, err := io.ReadAll(resp.Body) if err != nil { // 处理读取错误 } defer resp.Body.Close() // 用encoding/json解码 var token Token if err := json.Unmarshal(bodyBytes, &token); err != nil { // 处理解码错误 } // 或者用jsonpb解码(必须传指针) var tokenPb Token if err := jsonpb.Unmarshal(bytes.NewReader(bodyBytes), &tokenPb); err != nil { // 处理解码错误 }
方案2:确保单次读取响应体
如果不需要多次反序列化,仅选择一种解码方式,且保证响应体只被读取一次:
// 使用encoding/json var token Token defer resp.Body.Close() err := json.NewDecoder(resp.Body).Decode(&token) // 或者使用jsonpb(注意传指针) var tokenPb Token defer resp.Body.Close() err := jsonpb.Unmarshal(resp.Body, &tokenPb)
方案3:验证Protobuf生成工具版本
确保使用最新版的protoc-gen-go生成结构体,避免版本兼容问题导致的tag异常:
protoc --go_out=. token.proto
验证结果
采用上述方案后,反序列化得到的Token结构体字段值与原JSON响应完全匹配,转为JSON输出后结果正常:
{"token": {"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZCRXFDSnFER21hMUxLZUtoZUVxcSJ9.eyJpc3MiOiJodHRwczovL2Rldi0xY3p5M3Vpa21pNjJ0dWN4LnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw0OTAiLCJhdWQiOiJhdXRoLXNlcnZpY2Ui...", "scope": "read:sample", "expires_in": 86400, "token_type": "Bearer"}}
内容的提问来源于stack exchange,提问作者Alif Biswas
相关产品推荐
相关产品推荐

