Go实现Protocol Buffers与JSON转换:protojson

引言

本文主要介绍Google开源的工具库Protojson库如何Protocol Buffers与JSON进行转换,以及和标准库encoding/json的性能对比。

Protojson 简介

Protojson是Google针对Protocol Buffers数据格式的JSON编码库,为Go语言开发人员提供了便捷的工具和API,用于Protocol Buffers消息与JSON之间的转换。常用API:

go 复制代码
func Format(m proto.Message) string
func Marshal(m proto.Message) ([]byte, error)
func Unmarshal(b []byte, m proto.Message) error
type MarshalOptions
func (o MarshalOptions) Format(m proto.Message) string
func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error)
func (o MarshalOptions) MarshalAppend(b []byte, m proto.Message) ([]byte, error)
type UnmarshalOptions
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error

接着,我们通过一些实践例子来演示如何使用。

安装protoc

  1. 设置Go Env(window os)
cmd 复制代码
# go env -w GOPROXY=https://goproxy.cn
# go env -w GOBIN=%USERPROFILE%\go\bin

其中 USERPROFILE 为默认用户安装路径,example: C:\Users\jeffff

  1. 下载安装

下载适合自己os的protoc版本,复制到GOBIN目录下,下载链接 github.com/protocolbuf...

  1. 检查是否安装成功
cmd 复制代码
# protoc --version
libprotoc 24.3

安装protoc-gen-go

这里采用 go install安装,安装成功后会添加到GOBIN目录下

cmd 复制代码
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# protoc-gen-go --version
protoc-gen-go v1.31.0

Example

创建/example/example.proto 创建/exmpale/main.go

proto 复制代码
syntax = "proto3";

package example.pb;

option go_package = "./;pb";

message User {
  string id = 1;
  string name = 3;
  int32  age = 4;
  string real_name = 5;
  string date = 8;
}

执行如下命令,生成/example/example.pb.go文件

cmd 复制代码
#protoc --go_out=. example.proto 

创建main.go,快速实践各个API的功能

go 复制代码
package main

import (
    "fmt"
    "github.com/google/uuid"
    "github.com/luckytking/practices/libs/protojson/pb"
    "google.golang.org/protobuf/encoding/protojson"
)

func userInfo() *pb.User {
    return &pb.User{
       Id:       uuid.NewString(),
       Name:     uuid.NewString(),
       Age:      33,
       RealName: uuid.NewString(),
       Date:     "",
       //Date:     time.Now().Format(time.DateTime),
    }
}

func main() {
    info := userInfo()
    fmt.Println("data.Src:", info)
    format := protojson.Format(info)
    fmt.Println("data.Format:", format)
    marshal, err := protojson.Marshal(info)
    if err != nil {
       panic(err)
    }
    fmt.Println("data.Marshal:", string(marshal))
    user1 := &pb.User{}
    err = protojson.Unmarshal(marshal, user1)
    fmt.Println("data.Unmarshal:", user1)

    //marshalOpt :=
    marshal2, err := protojson.MarshalOptions{
       EmitUnpopulated: true,
    }.Marshal(info)
    if err != nil {
       panic(err)
    }
    fmt.Println("data.Marshal2:", string(marshal2))
    user2 := &pb.User{}
    err = protojson.Unmarshal(marshal2, user2)
    fmt.Println("data.Unmarshal2:", user2)
} 

输出如下:

js 复制代码
data.Src: id:"df8bbcca-d8b9-4e41-91ff-6ccf01567d27"  name:"67115015-48bb-4284-b601-e9348a53d40f"  age:33  real_name:"bed916f1-0fb3-413c-9de3-222cbc90c814"
data.Format: {
  "id":  "df8bbcca-d8b9-4e41-91ff-6ccf01567d27",
  "name":  "67115015-48bb-4284-b601-e9348a53d40f",
  "age":  33,
  "realName":  "bed916f1-0fb3-413c-9de3-222cbc90c814"
}
data.Marshal: {"id":"df8bbcca-d8b9-4e41-91ff-6ccf01567d27", "name":"67115015-48bb-4284-b601-e9348a53d40f", "age":33, "realName":"bed916f1-0fb3-413c-9de3-222cbc90c814"}
data.Unmarshal: id:"df8bbcca-d8b9-4e41-91ff-6ccf01567d27"  name:"67115015-48bb-4284-b601-e9348a53d40f"  age:33  real_name:"bed916f1-0fb3-413c-9de3-222cbc90c814"
data.Marshal2: {"id":"df8bbcca-d8b9-4e41-91ff-6ccf01567d27", "name":"67115015-48bb-4284-b601-e9348a53d40f", "age":33, "realName":"bed916f1-0fb3-413c-9de3-222cbc90c814", "date":""}
data.Unmarshal2: id:"df8bbcca-d8b9-4e41-91ff-6ccf01567d27"  name:"67115015-48bb-4284-b601-e9348a53d40f"  age:33  real_name:"bed916f1-0fb3-413c-9de3-222cbc90c814"

上例中:

  1. 通过 Marshal 或 MarshalOptions.Marshal 函数将 protobuf 转换为 JSON 格式.
  2. 通过 Unmarshal 或 MarshalOptions.Unmarshal 函数将JSON 格式的数据转换为 protobuf 消息.
  3. MarshalOptions 提供了一些自定义选项,例如例子中 "EmitUnpopulated: true," 是否输出未设置的字段. 这里虽然user.Data=""(默认值),但还是输出了空字符。

更多的option参考

go 复制代码
type MarshalOptions struct {
	pragma.NoUnkeyedLiterals

        // Multiline 指定封送拆收器是否应以缩进形式格式化输出,并将每个文本元素放在新行上。// 如果 Indent 是空字符串,则选择任意缩进。
	Multiline bool

        // Indent 指定在多行格式化输出中使用的缩进字符集,
	以便每个条目前面都有缩进,并且
	// 以换行符结尾。如果非空,则 Multiline 被视为 true。
	// 缩进只能由空格或制表符组成。
	Indent string

        // AllowPartial 允许对缺少必填字段的消息进行封送
	// 而不返回错误。如果AllowPartial 为 false(默认值),
	// 如果缺少任何必填字段,Marshal 将返回错误。
	AllowPartial bool

	// UseProtoNames 在 JSON字段名称中使用 proto 字段名称而不是小驼峰命名。
	UseProtoNames bool

        // UseEnumNumbers 将枚举值作为数字发出。
	UseEnumNumbers bool


        // EmitUnpopulated 指定是否发出未填充的字段。//它不会
	发出未填充的 oneof 字段或未填充的扩展字段。
	// 未填充字段发出的 JSON 值如下:
	//  ╔═══════╤════════════════════════════╗
	//  ║ JSON  │ Protobuf field             ║
	//  ╠═══════╪════════════════════════════╣
	//  ║ false │ proto3 boolean fields      ║
	//  ║ 0     │ proto3 numeric fields      ║
	//  ║ ""    │ proto3 string/bytes fields ║
	//  ║ null  │ proto2 scalar fields       ║
	//  ║ null  │ message fields             ║
	//  ║ []    │ list fields                ║
	//  ║ {}    │ map fields                 ║
	//  ╚═══════╧════════════════════════════╝
	EmitUnpopulated bool

	// 解析器用于在扩展 google.protobuf.Any
	// 消息时查找类型。如果为零,则默认使用 protoregistry.GlobalTypes。
	Resolver interface {
		protoregistry.ExtensionTypeResolver
		protoregistry.MessageTypeResolver
	}
}

性能对比 protojson VS encoding/json

创建example_test.go

go 复制代码
package main

import (
    "encoding/json"
    "google.golang.org/protobuf/encoding/protojson"
    "testing"
)

func BenchmarkProtoJson(b *testing.B) {
    gen := userInfo()
    for i := 0; i < b.N; i++ {
       protojson.Marshal(gen)
    }
}

func BenchmarkStdJson(b *testing.B) {
    gen := userInfo()
    for i := 0; i < b.N; i++ {
       json.Marshal(gen)
    }
} 

结论如下:

bash 复制代码
BenchmarkProtoJson
BenchmarkProtoJson-4      230895              4556 ns/op
BenchmarkStdJson
BenchmarkStdJson-4        715443              1732 ns/op

总结

本文通过实践例子介绍Protojson库实现Protocol Buffers与JSON之间的转换,以及其和标准库encoding/json性能对比。总的来说,利用Google Protocol Buffers定制API协议,和采用Protojson解决传输格式转换。在分布式系统无论是Rpc还是Http的网络通信,相信Protojson可以发挥不错的表现。

参考

相关推荐
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉8 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想