Я хочу преобразовать сериализованные сообщения protobuf в удобочитаемый формат JSON. Основная проблема, с которой я сталкиваюсь, заключается в том, что мне нужно сделать это без предварительной компиляции дескриптора proto в код Go. У меня есть доступ к файлам .proto
во время выполнения, но не во время компиляции.
У меня сложилось впечатление, что новый Protobuf API v2 (https://github.com/protocolbuffers/protobuf-go) поддерживает динамическую десериализацию (см. пакет types/dynamicpb
), но я не мог понять, как его использовать, по-видимому:
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)
}
}
Вышеприведенный код аннотирован моей проблемой: Как я могу получить требуемый дескриптор для dynamicpb.NewMessage()
из .proto
файла?
Возможно, другие библиотеки поддерживают такую функциональность? В каком смысле библиотека protobuf является «динамической»?
@kentor: это первый раз, когда я слышу об этом, но кажется, что просто RTFMing позволяет вам создавать динамические типы сообщений самостоятельно. Хотя я не вижу поддержки чтения из файла .proto
.
Поскольку он создает динамические типы на основе этого, может быть, этот пакет может прочитать файл .proto для вас?
Может не быть библиотеки, поддерживающей это; если у вас есть файл .proto, вы просто скомпилируете его в приложение, поэтому вариант использования, когда у вас есть файл .proto во время выполнения, но не во время компиляции, довольно необычен.
Этот вопрос довольно интересен. Я проделал некоторые работы с пробками protobuf. Насколько я могу судить, нужен дополнительный кли, потому что мы не хотим «изобретать велосипед».
Шаг первый, нам нужен protoc для преобразования файла «.proto» в некоторый формат, чтобы мы могли легко получить «protoreflect.MessageDescriptor».
Этот плагин предназначен для получения необработанных байтов, которые протокол отправляет другим плагинам в качестве входных данных.
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}
Так должно работать с пакетом 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
}
Загрузка дескриптора из файла .proto во время выполнения не поддерживается.