Go实现Protocol Buffers与JSON转换:protojson

2023年 9月 24日 135.6k 0

引言

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

Protojson 简介

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

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

  • 设置Go Env(window os)
  • # go env -w GOPROXY=https://goproxy.cn
    # go env -w GOBIN=%USERPROFILE%\go\bin
    

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

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

  • 检查是否安装成功
  • # protoc --version
    libprotoc 24.3
    

    安装protoc-gen-go

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

    # 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

    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文件

    #protoc --go_out=. example.proto 
    

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

    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)
    } 
    

    输出如下:

    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"
    
    

    上例中:

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

    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

    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)
        }
    } 
    

    结论如下:

    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可以发挥不错的表现。

    参考

    • protojson doc
    • protobuf-go
    • protobuf-go:encoding/protojson
    • 博客:每日一库:protojson
    • 浅谈kratos的表现层源码实现与解码器

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论