You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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": ""}}

问题根源

  1. 响应体重复读取未重置:HTTP响应体resp.Body是流式读取对象,第一次读取后指针会移至末尾,后续读取会从末尾开始,导致解析时数据错位,出现字段值混乱。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 18:55:26