文章

protojson解析库

json 和 protojson使用

不是直接使用encoding/json库的原因:

序列化器能否用于 Protobuf GO 结构体输出标准 Protobuf JSON能正确处理特殊 proto 类型
encoding/json可以,但有限
protojson.Marshal可以

解析区别

1
MsgId string `protobuf:"bytes,1,opt,name=msg_id,json=msgId,proto3"  json:"msg_id,omitempty"`
使用场景使用的标签使用字段示例
protojson + UseProtoNames: trueprotobuf 标签中的name=msg_id“msg_id”{“msg_id”: “123”}
protojson + UseProtoNames: falseprotobuf 标签中的 json=msgId“msgId”{“msgId”: “123”}
encoding/jsonjson:”msg_id” 标签“msg_id”{“msg_id”: “123”}

protojson 库

相关的包:

1
google.golang.org/protobuf/encoding/protojson

语法

1
2
3
4
5
//
defaultOpts := protojson.MarshalOptions{}
data2, _ := defaultOpts.Marshal(待解析)
// 
protojson.Unmarshal([]byte(待解析proto数据), &结果)

案例

protobuf 定义

1
2
3
4
5
6
7
8
9
10
11
12
message ActionInfo {
    string action_id = 1;        // protobuf 字段名:action_id
    bool only_client = 10;        // protobuf 字段名:only_client
    ActionType action_type = 5;  // 枚举类型
}

enum ActionType {
  // 未指定的动作类型
  ACTION_TYPE_UNSPECIFIED = 0;
  // 发送消息动作
  ACTION_TYPE_SEND_MSG = 1;
}

生成的go代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type ActionInfo struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// 动作类型
	ActionType ActionType `protobuf:"varint,1,opt,name=action_type,json=actionType,proto3,enum=xdproto.common_business.msg_adapter.ActionType" json:"action_type,omitempty"`
	// 内部生成的 ID
	ActionId string `protobuf:"bytes,9,opt,name=action_id,json=actionId,proto3" json:"action_id,omitempty"`
	// 是否只能客户端发送
	OnlyClient bool `protobuf:"varint,10,opt,name=only_client,json=onlyClient,proto3" json:"only_client,omitempty"`
}

type ActionType int32
const (
	// 未指定的动作类型
	ActionType_ACTION_TYPE_UNSPECIFIED ActionType = 0
	// 发送消息动作
	ActionType_ACTION_TYPE_SEND_MSG ActionType = 1
)

使用配置(UseProtoNames: true, UseEnumNumbers: true)

var (
	marshalOpts = protojson.MarshalOptions{
		UseProtoNames:  true,
		UseEnumNumbers: true,
	}
)

序列化结果:

1
2
3
4
5
"action_info": {                 //  使用 protobuf 原始字段名 action_info
    "action_id": "action_456",      //  嵌套的 ActionInfo 也使用 action_id
    "only_client": true,          //  使用 protobuf 原始字段名 only_client
    "action_type": 1             //  枚举值使用数字 1(不是字符串)
}

使用默认配置(UseProtoNames: false, UseEnumNumbers: false)

1
2
3
// 默认配置
defaultOpts := protojson.MarshalOptions{}
data, _ := defaultOpts.Marshal(callBackMsg)

序列化结果:

1
2
3
4
5
"actionInfo": {                   //  使用 JSON 命名(驼峰式)
    "actionId": "action_456",        //  嵌套的 ActionInfo 也使用 actionId
    "onlyClient": true,            //  使用 JSON 命名(驼峰式)
    "actionType": "ACTION_TYPE_TEXT"   //  枚举值使用字符串名称
} 

总结

image-20251129175112804

1
body: { "msg_id": "1774405167106521", "request_id": "", "user_name": "顺然旗舰店", "vender_id": "", "app": "mindflow", "shop_id": "662f5c2dc38b2037c947a2bc", "from_pin": "顺然旗舰店:慕斯", "to_pin": "one_id_299502931", "action_list": [{ "action_type": "ACTION_TYPE_SEND_MSG", "timestamp": "1774405139496", "timeout": 1, "send_msg_action": { "msg_info": { "msg_type": "SEND_MSG_TYPE_TEXT", "msg_sub_type": "SEND_MSG_SUB_TYPE_DEFAULT", "text": { "content": "好的呢~" }, "ref_msg_id": "" }, "delay": 0, "confirm": false, "is_not_clear_timing": false, "is_not_clear_light": false }, "business": "BUSINESS_ROBOT", "action_id": "900322a1-ddf8-483d-b295-2b889e8f76e1", "only_client": false, "extra_data": { "channel": "pulsar", "msg_scenes_source": "7.18.0" } }], "platform": "tb", "plat_shop_id": "662f5c2dc38b2037c947a2bc", "chat_type": "CHAT_SESSION_TYPE_UNKNOWN", "plat_session_id": "" }
1
2
3
4
// protobuf tag 中的定义
protobuf:"bytes,1,opt,name=msg_id,json=msgId,proto3"
//                      ↑            ↑
//                   原始名       JSON 映射名

protojson.Unmarshal 的智能行为 关键点:protojson 会同时尝试匹配两种格式! 优先匹配 JSON 名称(json=msgId) 如果找不到,会尝试 protobuf 原始名称(name=msg_id)

rotojson.Unmarshal 确实会同时尝试两种名称格式,并且遵循JSON 名称优先的匹配策略。

匹配优先级(从高到低)

优先级名称类型来源示例
1JSON 名称json_name 选项 或 驼峰自动转换msgId
2Protobuf 原始名称.proto 文件中的字段名msg_id

未知字段

默认配置(当前代码)

err = protojson.Unmarshal(body, req)

DiscardUnknown: false(默认) 行为:会报错,提示遇到未知字段

显式忽略未知字段

unmarshalOpt := protojson.UnmarshalOptions{ DiscardUnknown: true, // ← 忽略未知字段 } err = unmarshalOpt.Unmarshal(body, req)

行为:不会报错,直接忽略 unknown_field

严格模式

unmarshalOpt := protojson.UnmarshalOptions{ DiscardUnknown: false, // 不忽略 AllowPartial: false, // 严格要求所有必填字段 } err = unmarshalOpt.Unmarshal(body, req)

行为:会报错,如果有未知字段或缺少必填字段

© 2024- lfj