这是一个完整的 Protobuf + ProtJSON 格式化演示项目,重点展示 google.golang.org/protobuf/encoding/protojson 包的使用方法和各种配置选项。
# 确保已安装 Go (1.19+)
go version
# 确保已安装 protoc 编译器
protoc --version
# 安装必要的 protoc 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest# 克隆项目
git clone <your-repo-url>
cd protoc-test
# 安装依赖
go mod tidy
# 生成 protobuf 代码
make proto# 运行完整演示
go run cmd/main.go
# 或者
make proto && cd cmd && go run main.go程序将展示 5 个主要演示部分:
{
"userId": "user-12345",
"username": "张三",
"totalPoints": "9223372036854775807", // uint64 → 字符串
"createdAt": "2023-01-15T10:30:00Z", // snake_case → camelCase
// ... 更多字段
}{
"user_id": "user-12345",
"created_at": "2023-01-15T10:30:00Z",
"total_points": "9223372036854775807",
// ... 保持原始 proto 字段命名
}{"userId":"user-12345","username":"张三","totalPoints":"9223372036854775807"...}{
"status": 1, // 使用数字而非字符串 "USER_STATUS_ACTIVE"
// ... 其他字段
}{
"users": [...],
"totalCount": "999999999999999999", // 大 uint64 值转为字符串
// ... 展示各种 uint64 字段的字符串转换
}&protojson.MarshalOptions{
UseProtoNames: false, // false=小驼峰, true=snake_case
EmitDefaultValues: true, // 是否包含默认值
UseEnumNumbers: false, // false=枚举名称, true=枚举数字
Indent: " ", // JSON 缩进格式
}&protojson.UnmarshalOptions{
DiscardUnknown: true, // 忽略未知字段
AllowPartial: false, // 是否允许部分消息
}protoc-test/
├── protos/
│ └── user.proto # Protobuf 定义文件
├── gen/go/user/v1/
│ ├── user.pb.go # 生成的 Go 代码
│ └── user_grpc.pb.go # 生成的 gRPC 代码
├── cmd/
│ └── main.go # 演示程序
├── Makefile # 构建脚本
├── go.mod # Go 模块定义
└── README.md # 项目说明
运行程序后,请重点观察:
- ✅
totalPoints字段显示为字符串格式 - ✅
userId使用小驼峰命名而非user_id - ✅ 枚举值可在字符串和数字间切换
- ✅ 错误输入得到合理的错误提示
- ✅ 未知字段被正确忽略(如果配置了
DiscardUnknown)
示例结果:
% go run cmd/main.go
Protobuf ProtJSON 完整演示
重点展示: google.golang.org/protobuf/encoding/protojson
关键特性: 小驼峰命名 + uint64转字符串
============================================================
=== Protobuf ProtJSON 格式化演示 ===
1. 标准配置 (小驼峰命名, uint64转字符串, 包含默认值):
配置: UseProtoNames=false, EmitDefaultValues=true
{
"userId": "user-12345",
"username": "张三",
"email": "zhangsan@example.com",
"age": 25,
"accountBalanceCents": "150000",
"loginCount": 42,
"totalPoints": "9223372036854775807",
"height": 175.5,
"weight": 68.5,
"isVerified": true,
"isPremium": false,
"status": "USER_STATUS_ACTIVE",
"createdAt": "2023-01-15T10:30:00Z",
"lastLogin": "2024-12-10T14:20:00Z",
"sessionTimeout": "1800s",
"homeAddress": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
"tags": [
"vip",
"early_adopter",
"beta_tester"
],
"favoriteNumbers": [
7,
13,
42
],
"addresses": [
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
{
"street": "456 Tech Ave",
"city": "Palo Alto",
"state": "CA",
"zipCode": "94301",
"country": "US"
}
],
"metadata": {
"campaign_id": "summer2024",
"device_type": "iPhone",
"referrer": "google",
"source": "mobile_app"
},
"scores": {
"english": 88,
"math": 95,
"science": 92
},
"nickname": "小张",
"luckyNumber": 888,
"acceptsMarketing": true,
"profilePicture": "ZmFrZV9pbWFnZV9kYXRhX2hlcmU=",
"phone": "+86-138-0013-8000"
}
2. 原始字段名配置 (snake_case 命名):
配置: UseProtoNames=true
{
"user_id": "user-12345",
"username": "张三",
"email": "zhangsan@example.com",
"age": 25,
"account_balance_cents": "150000",
"login_count": 42,
"total_points": "9223372036854775807",
"height": 175.5,
"weight": 68.5,
"is_verified": true,
"is_premium": false,
"status": "USER_STATUS_ACTIVE",
"created_at": "2023-01-15T10:30:00Z",
"last_login": "2024-12-10T14:20:00Z",
"session_timeout": "1800s",
"home_address": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip_code": "94102",
"country": "US"
},
"tags": [
"vip",
"early_adopter",
"beta_tester"
],
"favorite_numbers": [
7,
13,
42
],
"addresses": [
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip_code": "94102",
"country": "US"
},
{
"street": "456 Tech Ave",
"city": "Palo Alto",
"state": "CA",
"zip_code": "94301",
"country": "US"
}
],
"metadata": {
"campaign_id": "summer2024",
"device_type": "iPhone",
"referrer": "google",
"source": "mobile_app"
},
"scores": {
"english": 88,
"math": 95,
"science": 92
},
"nickname": "小张",
"lucky_number": 888,
"accepts_marketing": true,
"profile_picture": "ZmFrZV9pbWFnZV9kYXRhX2hlcmU=",
"phone": "+86-138-0013-8000"
}
3. 紧凑格式 (省略默认值, 无缩进):
配置: EmitDefaultValues=false, Indent=""
{"userId":"user-12345", "username":"张三", "email":"zhangsan@example.com", "age":25, "accountBalanceCents":"150000", "loginCount":42, "totalPoints":"9223372036854775807", "height":175.5, "weight":68.5, "isVerified":true, "status":"USER_STATUS_ACTIVE", "createdAt":"2023-01-15T10:30:00Z", "lastLogin":"2024-12-10T14:20:00Z", "sessionTimeout":"1800s", "homeAddress":{"street":"123 Main St", "city":"San Francisco", "state":"CA", "zipCode":"94102", "country":"US"}, "tags":["vip", "early_adopter", "beta_tester"], "favoriteNumbers":[7, 13, 42], "addresses":[{"street":"123 Main St", "city":"San Francisco", "state":"CA", "zipCode":"94102", "country":"US"}, {"street":"456 Tech Ave", "city":"Palo Alto", "state":"CA", "zipCode":"94301", "country":"US"}], "metadata":{"campaign_id":"summer2024", "device_type":"iPhone", "referrer":"google", "source":"mobile_app"}, "scores":{"english":88, "math":95, "science":92}, "nickname":"小张", "luckyNumber":888, "acceptsMarketing":true, "profilePicture":"ZmFrZV9pbWFnZV9kYXRhX2hlcmU=", "phone":"+86-138-0013-8000"}
4. 枚举数字格式:
配置: UseEnumNumbers=true
{
"userId": "user-12345",
"username": "张三",
"email": "zhangsan@example.com",
"age": 25,
"accountBalanceCents": "150000",
"loginCount": 42,
"totalPoints": "9223372036854775807",
"height": 175.5,
"weight": 68.5,
"isVerified": true,
"isPremium": false,
"status": 1,
"createdAt": "2023-01-15T10:30:00Z",
"lastLogin": "2024-12-10T14:20:00Z",
"sessionTimeout": "1800s",
"homeAddress": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
"tags": [
"vip",
"early_adopter",
"beta_tester"
],
"favoriteNumbers": [
7,
13,
42
],
"addresses": [
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
{
"street": "456 Tech Ave",
"city": "Palo Alto",
"state": "CA",
"zipCode": "94301",
"country": "US"
}
],
"metadata": {
"campaign_id": "summer2024",
"device_type": "iPhone",
"referrer": "google",
"source": "mobile_app"
},
"scores": {
"english": 88,
"math": 95,
"science": 92
},
"nickname": "小张",
"luckyNumber": 888,
"acceptsMarketing": true,
"profilePicture": "ZmFrZV9pbWFnZV9kYXRhX2hlcmU=",
"phone": "+86-138-0013-8000"
}
5. 重点演示:uint64 字段自动转为字符串
注意观察 totalPoints 和 totalCount 字段:
{
"users": [
{
"userId": "user-12345",
"username": "张三",
"email": "zhangsan@example.com",
"age": 25,
"accountBalanceCents": "150000",
"loginCount": 42,
"totalPoints": "9223372036854775807",
"height": 175.5,
"weight": 68.5,
"isVerified": true,
"isPremium": false,
"status": "USER_STATUS_ACTIVE",
"createdAt": "2023-01-15T10:30:00Z",
"lastLogin": "2024-12-10T14:20:00Z",
"sessionTimeout": "1800s",
"homeAddress": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
"tags": [
"vip",
"early_adopter",
"beta_tester"
],
"favoriteNumbers": [
7,
13,
42
],
"addresses": [
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"country": "US"
},
{
"street": "456 Tech Ave",
"city": "Palo Alto",
"state": "CA",
"zipCode": "94301",
"country": "US"
}
],
"metadata": {
"campaign_id": "summer2024",
"device_type": "iPhone",
"referrer": "google",
"source": "mobile_app"
},
"scores": {
"english": 88,
"math": 95,
"science": 92
},
"nickname": "小张",
"luckyNumber": 888,
"acceptsMarketing": true,
"profilePicture": "ZmFrZV9pbWFnZV9kYXRhX2hlcmU=",
"phone": "+86-138-0013-8000"
},
{
"userId": "user-67890",
"username": "李四",
"email": "lisi@example.com",
"age": 30,
"accountBalanceCents": "0",
"loginCount": 0,
"totalPoints": "18446744073709551615",
"height": 0,
"weight": 0,
"isVerified": false,
"isPremium": false,
"status": "USER_STATUS_INACTIVE",
"createdAt": "2023-06-20T15:45:00Z",
"tags": [
"regular_user"
],
"favoriteNumbers": [],
"addresses": [],
"metadata": {},
"scores": {},
"profilePicture": "",
"wechat": "lisi_wechat"
}
],
"totalCount": "999999999999999999",
"nextPageToken": "next_page_token_12345"
}
=== Protobuf ProtJSON 反序列化演示 ===
1. 输入的 JSON 数据:
{
"userId": "user-99999",
"username": "王五",
"email": "wangwu@example.com",
"age": 28,
"totalPoints": "9223372036854775807",
"isVerified": true,
"status": "USER_STATUS_ACTIVE",
"createdAt": "2023-03-15T08:30:00Z",
"homeAddress": {
"street": "789 Demo Road",
"city": "Beijing",
"country": "CN"
},
"tags": ["test", "demo"],
"metadata": {
"source": "web",
"lang": "zh-CN"
},
"phone": "+86-139-0013-9000"
}
2. 反序列化成功! 解析后的 Go 结构体:
用户ID: user-99999
用户名: 王五
邮箱: wangwu@example.com
年龄: 28
总积分: 9223372036854775807 (uint64)
状态: USER_STATUS_ACTIVE
地址: Beijing, CN
标签: [test demo]
联系方式: 电话 +86-139-0013-9000
3. 再次序列化验证数据完整性:
{
"userId": "user-99999",
"username": "王五",
"email": "wangwu@example.com",
"age": 28,
"accountBalanceCents": "0",
"loginCount": 0,
"totalPoints": "9223372036854775807",
"height": 0,
"weight": 0,
"isVerified": true,
"isPremium": false,
"status": "USER_STATUS_ACTIVE",
"createdAt": "2023-03-15T08:30:00Z",
"homeAddress": {
"street": "789 Demo Road",
"city": "Beijing",
"state": "",
"zipCode": "",
"country": "CN"
},
"tags": [
"test",
"demo"
],
"favoriteNumbers": [],
"addresses": [],
"metadata": {
"lang": "zh-CN",
"source": "web"
},
"scores": {},
"profilePicture": "",
"phone": "+86-139-0013-9000"
}
=== 错误处理和边界情况演示 ===
1. 处理无效 JSON:
预期错误: proto: (line 1:27): invalid value for int32 field age: "not_a_number"
2. 处理未知字段(配置了 DiscardUnknown=true):
成功解析,未知字段被忽略
用户ID: test-user, 用户名: TestUser
3. uint64 边界值处理:
输入: {"totalPoints": "18446744073709551615"}
成功: totalPoints = 18446744073709551615
演示完成! 关键要点总结:
✓ protojson.MarshalOptions 提供丰富的配置选项
✓ UseProtoNames=false 实现小驼峰命名 (userId vs user_id)
✓ uint64 字段自动转换为 JSON 字符串,避免精度丢失
✓ 支持所有 protobuf 数据类型,包括嵌套、重复、映射等
✓ 提供完善的错误处理和边界情况支持