当前位置: 首页 > 知识库问答 >
问题:

将protobuf序列化消息转换为JSON,而无需预编译Go代码

吴宝
2023-03-14

我想将protobuf序列化消息转换为人类可读的JSON格式。我面临的主要问题是,我需要这样做,而无需事先将proto描述符编译成Go代码。我有权访问。proto文件在运行时,但不是在编译时。

我觉得新的Protobuf API v2(https://github.com/protocolbuffers/protobuf-go)支持动态反序列化(请参阅包类型/动态CPB),但我显然不知道如何使用它:

func readDynamically(in []byte) {
    // How do I load the required descriptor (for NewMessage()) from my `addressbook.proto` file?)
    descriptor := ??

    msg := dynamicpb.NewMessage(descriptor)
    err := protojson.Unmarshal(in, msg)
    if err != nil {
        panic(err)
    }
}

上面的代码带有我的问题:如何获得动态CPB所需的描述符。NewMessage()来自。proto文件?


共有2个答案

酆茂才
2023-03-14

这个问题有点有趣。我在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\u out=/pb/服务器proto,您将退出。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}
索寒
2023-03-14

应该像这样使用dynamicpb包。

func readDynamically(in []byte) {
    registry, err := createProtoRegistry(".", "addressbook.proto")
    if err != nil {
        panic(err)
    }

    desc, err := registry.FindFileByPath("addressbook.proto")
    if err != nil {
        panic(err)
    }
    fd := desc.Messages()
    addressBook := fd.ByName("AddressBook")

    msg := dynamicpb.NewMessage(addressBook)
    err = proto.Unmarshal(in, msg)
    jsonBytes, err := protojson.Marshal(msg)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(jsonBytes))
    if err != nil {
        panic(err)
    }
}

func createProtoRegistry(srcDir string, filename string) (*protoregistry.Files, error) {
    // Create descriptors using the protoc binary.
    // Imported dependencies are included so that the descriptors are self-contained.
    tmpFile := filename + "-tmp.pb"
    cmd := exec.Command("./protoc/protoc",
        "--include_imports",
        "--descriptor_set_out=" + tmpFile,
        "-I"+srcDir,
        path.Join(srcDir, filename))

    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    err := cmd.Run()
    if err != nil {
        return nil, err
    }
    defer os.Remove(tmpFile)

    marshalledDescriptorSet, err := ioutil.ReadFile(tmpFile)
    if err != nil {
        return nil, err
    }
    descriptorSet := descriptorpb.FileDescriptorSet{}
    err = proto.Unmarshal(marshalledDescriptorSet, &descriptorSet)
    if err != nil {
        return nil, err
    }

    files, err := protodesc.NewFiles(&descriptorSet)
    if err != nil {
        return nil, err
    }

    return files, nil
}
 类似资料:
  • 我试图阅读和打印从Kafka使用Apache Flink的原型消息。 我遵循官方文件,但没有成功:https://nightlies.apache.org/flink/flink-docs-master/docs/dev/datastream/fault-tolerance/serialization/third_party_serializers/ Flink消费者代码是: 反序列化器代码是:

  • 有必要将通用GRPC服务器实现为存根。 找不到与服务器类似的内容,仅针对客户端。 非常感谢!

  • 问题内容: 我用过这种方法 它正在打印“ jsonObject为null”。 “ error:nil”有什么问题吗? 我没有使用任何网址或连接方法。 我有一个json文件,我想在表中显示它。 问题答案: 请尝试以下代码。

  • 问题内容: 我正在尝试使用该包将Go结构转换为JSON,但我得到的只是。我敢肯定这是完全显而易见的,但我看不到。 然后,当我尝试运行它时,我得到以下信息: 问题答案: 您需要导出的字段,以便在包装可以看到它。将字段重命名为。 输出:

  • 问题内容: 这个下课 需要转换为 我尝试将对象手动传递给将变量设置为字典并返回字典的函数。但是,我希望有一种更好的方法来实现这一目标。 问题答案: 在Swift 4中,您可以从类型继承。

  • 我正在尝试将单个输入消息转换为多个消息。我有一个带有以下签名的方法: 类类似于: 对于中的每个,我想创建一个的实例。我如何做到这一点并处理