Go Protobuf 資源的可讀化

工作上有大量協(xié)議采用 Google Protocol Buffer,關于 Protobuf 的簡單介紹可以看 IBM 的《Google Protocol Buffer 的使用和原理》這篇介紹。簡單來說,Protobuf 的優(yōu)點是(相比 XML)更小、更快、更簡單,同時可以向后兼容。缺點的話,對我日常工作影響比較大的就是可讀性較差,因為 Protobuf 壓縮的時候會做序列化,生成 pb 文件,這個文件是二進制的,無法做到 human readable。但在日常工作中,尤其是排查問題是,經(jīng)常需要看資源文件內(nèi)容是否正確、上下游服務收發(fā)包內(nèi)容是否正確、偽造 pb 資源等等,這些內(nèi)容都是 pb 的,需要經(jīng)過轉(zhuǎn)換才能讀懂,由此就用 Go 寫了利用 JSON 偽造 pb 資源和反序列化 pb 打印成人類可讀的文本的兩段程序。

JSON 轉(zhuǎn) pb

這個感覺起來是件很麻煩的事情,但是有了 jsonpb 這個庫之后,事情就變得很簡單了。

首先定義 user.proto 。

syntax = "proto3";

package user_info;

message UserInfo {
    message User {
        string username = 1;
        uint32 age      = 2;
        string graduate = 3;
    }
    
    repeated User user_list = 1;
}

然后再轉(zhuǎn)換生成 user.pb.go 文件。

protoc --go_out=. user.proto

編寫 JSON 文件,注意 key 的名字需要遵循 user.pb.go 中的名字,例如:

type UserInfo struct {
    UserList []*UserInfo_User `protobuf:"bytes,1,rep,name=user_list,json=userList" json:"user_list,omitempty"`
}

type UserInfo_User struct {
    Username string `protobuf:"bytes,1,opt,name=username" json:"username,omitempty"`
    Age      uint32 `protobuf:"varint,2,opt,name=age" json:"age,omitempty"`
    Graduate string `protobuf:"bytes,3,opt,name=graduate" json:"graduate,omitempty"`
}

user.pb.go 已經(jīng)指定了一個 field 在 JSON 中的命名,直接按照這個編寫 JSON 文件即可。

{
  "userList": [
    {
      "username": "lawrencelin",
      "age": 28,
      "graduate": "Tongji University"
    },
    {
      "username": "findingsea",
      "age": 28,
      "graduate": "Fudan University"
    }
  ]
}

編寫主代碼:

package main

import (
    "github.com/golang/protobuf/proto"
    "io/ioutil"
    "os"
    "fmt"
    "github.com/golang/protobuf/jsonpb"
    "user_proto"
)

func main()  {
    jsonFilePath := "/home/lawrence/GoglandProjects/JsonToPbIntro/json/user_info.json"
    pbFilePath := "/home/lawrence/GoglandProjects/JsonToPbIntro/pb/user_info.pb"

    buf, err := ioutil.ReadFile(jsonFilePath)
    if err != nil {
        fmt.Println("Read file err: ", err)
        os.Exit(0)
    }

    userInfo := &user_info.UserInfo{}

    if err = jsonpb.UnmarshalString(string(buf), userInfo); err != nil {
        fmt.Println("jsonpb UnmarshalString fail: ", err)
        os.Exit(0)
    }

    fmt.Println("user info pb: ", userInfo.String())

    data, err := proto.Marshal(userInfo)
    if err != nil {
        fmt.Println("proto Marshal fail: ", err)
        os.Exit(0)
    }

    if err = ioutil.WriteFile(pbFilePath, data, os.ModePerm); err != nil {
        fmt.Println("Write file err: ", err)
    }
}

核心函數(shù)就是 UnmarshalString ,輸入是 JSON 字符串,輸出 Protobuf 對象。

func UnmarshalString(str string, pb proto.Message) error

運行一下 main.go,就生成好了 user_info.pb 文件,打印如下:

user info pb:  user_list:<username:"lawrencelin" age:28 graduate:"Tongji University" > user_list:<username:"findingsea" age:28 graduate:"Fudan University" > 

打印 Protobuf 對象

這一邊本來應該很簡單的,因為 Protobuf 庫就提供了字符串轉(zhuǎn)換函數(shù),像 C++ 版 Protobuf 直接提供了 DebugString() 方法,可以直接輸出可讀的打印字符串。但是 Go 里面,我直覺反應調(diào)用了一下 String() 方法,fmt.Println("user info pb: ", userInfo.String()),發(fā)現(xiàn)只能打印成一行。

user_list:<username:"lawrencelin" age:28 graduate:"Tongji University" > user_list:<username:"findingsea" age:28 graduate:"Fudan University" > 

看了一下 String() 方法的實現(xiàn),直接調(diào)用了 CompactTextString 方法:

func (m *UserInfo) String() string            { return proto.CompactTextString(m) }

// CompactText writes a given protocol buffer in compact text format (one line).
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }

// CompactTextString is the same as CompactText, but returns the string directly.
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }

注釋里說明了這個接口只能返回壓縮過的文本,這個可讀性就很差了,那如何輸出可讀的 Protobuf 對象呢?

看了文檔之后,發(fā)現(xiàn)應該使用 MarshalTextString 接口,就可以直接返回可讀的文本格式 Protobuf 對象。其接口源碼和注釋如下:

// MarshalText writes a given protocol buffer in text format.
// The only errors returned are from w.
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }

// MarshalTextString is the same as MarshalText, but returns the string directly.
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }

調(diào)用的方法很簡單,fmt.Println(proto.MarshalTextString(userInfo)),輸出:

user_list: <
  username: "lawrencelin"
  age: 28
  graduate: "Tongji University"
>
user_list: <
  username: "findingsea"
  age: 28
  graduate: "Fudan University"
>
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • Golang是我最喜歡的一門語言,它簡潔、高效、易學習、開發(fā)效率高、還可以編譯成機器碼… 雖然它一出世,就飽受關注...
    盤木閱讀 3,574評論 0 7
  • 我們都聽過一萬小時定律,就是通過一萬小時的練習成為一方領域的專家,所用時間大約是十年,我們現(xiàn)在想學的技能太...
    兔媽媽1503閱讀 2,176評論 8 8
  • 天喜自由 但笑的大方 哭的磊落 生性孤僻 卻落落不群 俠骨柔情 單槍匹馬也能所向披靡 在權勢面前不減狂驕 在高貴面...
    于沛龍閱讀 159評論 0 0