这个问题有点意思。我在 protobuf 插件上做了一些工作。据我所知,需要额外的 cli,因为我们不想“重新发明轮子”。


第一步,我们需要 protoc 将“.proto”文件转换为某种格式,这样我们就可以轻松获得“protoreflect.MessageDescriptor”。


该插件用于获取 protoc 作为输入发送给其他插件的原始字节。


package main


import (

    "fmt"

    "io/ioutil"

    "os"

)


func main() {

    if len(os.Args) == 2 && os.Args[1] == "--version" {

        // fmt.Fprintf(os.Stderr, "%v %v\n", filepath.Base(os.Args[0]), version.String())

        os.Exit(0)

    }


    in, err := ioutil.ReadAll(os.Stdin)

    if err != nil {

        fmt.Printf("error: %v", err)

        return

    }

    ioutil.WriteFile("./out.pb", in, 0755)

}

构建并重命名为protoc-gen-raw,然后生成protoc --raw_out=./pb ./server.proto,你会得到out.pb. 从现在开始忘记你的“.proto”文件,把这个“out.pb”放在你打算放“.proto”的地方。我们得到的是这个 .pb 文件的官方支持。


第 2 步:将 protobuf 序列化消息反序列化为 JSON。


package main


import (

    "fmt"

    "io/ioutil"


    "google.golang.org/protobuf/proto"

    "google.golang.org/protobuf/compiler/protogen"

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

    "google.golang.org/protobuf/types/dynamicpb"

    "google.golang.org/protobuf/types/pluginpb"

)


func main() {

    in, err := ioutil.ReadFile("./out.pb")

    if err != nil {

        fmt.Printf("failed to read proto file: %v", err)

        return

    }

    req := &pluginpb.CodeGeneratorRequest{}

    if err := proto.Unmarshal(in, req); err != nil {

        fmt.Printf("failed to unmarshal proto: %v", err)

        return

    }

    gen, err := protogen.Options{}.New(req)

    if err != nil {

        fmt.Printf("failed to create new plugin: %v", err)

        return

    }


    // serialize protobuf message "ServerConfig"

    data := &ServerConfig{

        GameType: 1,

        ServerId: 105,

        Host:     "host.host.host",

        Port:     10024,

    }

    raw, err := data.Marshal()

    if err != nil {

        fmt.Printf("failed to marshal protobuf: %v", err)

        return

    }


    for _, f := range gen.Files {

        for _, m := range f.Messages {


            // "ServerConfig" is the message name of the serialized message

            if m.GoIdent.GoName == "ServerConfig" {

                // m.Desc is MessageDescriptor

                msg := dynamicpb.NewMessage(m.Desc)

                // unmarshal []byte into proto message

                err := proto.Unmarshal(raw, msg)

                if err != nil {

                    fmt.Printf("failed to Unmarshal protobuf data: %v", err)

                    return

                }

                // marshal message into json

                jsondata, err := protojson.Marshal(msg)

                if err != nil {

                    fmt.Printf("failed to Marshal to json: %v", err)

                    return

                }

                fmt.Printf("out: %v", string(jsondata))

            }

        }

    }

}

// the output is:

// out: {"gameType":1, "serverId":105, "host":"host.host.host", "port":10024}