簡介
它的作用正如它的名字,是一個用來反射proto
文件的庫。
倉庫原地址:https://github.com/jhump/protoreflect
來自官方的介紹
This repo provides reflection APIs for protocol buffers (also known as "protobufs" for short) and gRPC. The core of reflection in protobufs is the descriptor. A descriptor is itself a protobuf message that describes a
.proto
source file or any element therein. So a collection of descriptors can describe an entire schema of protobuf types, including RPC services.
此存儲庫為 protobuf
和gRPC
提供了反射API。 protobuf 中反射的核心是descriptor
。 descriptor
本身就是protobuf message
,它描述 proto
源文件或其中的元素。 因此,descriptor
的集合可以描述protobuf類型的整個架構,包括RPC服務。
使用方式
首先創建一個 protoparse.Parser
,利用這個 Parser 去 parse 你的 proto 文件,得到 desc.FileDescriptor
。之后根據 FileDescriptor 中的方法來提取出里面的 message
就好了。
包內的重要類型
Parser
parser
就是用來分析文件的
// Parser parses proto source into descriptors.
type Parser struct {
// ...
}
// parse 文件並得到該文件的 FileDescriptor
func (p Parser) ParseFiles(filenames ...string) ([]*desc.FileDescriptor, error){
// ...
}
FileDescriptor
它是對一個 proto 文件的描述。
它提供了很多 method,用於獲取 proto 文件里的情況,函數的作用看名字就可以知道了。
目前我常用的就這四個 method。
GetMessageTypes() []*MessageDescriptor // 獲取文件內所有的 message 類型
GetEnumTypes() []*EnumDescriptor // 獲取文件內所有的 enum 類型
FindMessage(msgName string) *MessageDescriptor // 根據名字來獲取 message 類型
FindEnum(enumName string) *EnumDescriptor // 根據名字來獲取 enum 類型
MessageDescriptor
它是對一個 proto message 的描述,也提供了很多 method。
只列出我常用的:
GetName() string // message 的名字
GetFullyQualifiedName() string // message 的全限定名
AsDescriptorProto() *descriptor.DescriptorProto // AsDescriptorProto returns the underlying descriptor proto
GetFields() []*FieldDescriptor // 獲取 message 內的所有字段
GetNestedMessageTypes() []*MessageDescriptor // 獲取在 message 內內嵌的 message
GetNestedEnumTypes() []*EnumDescriptor
FindFieldByName(fieldName string) *FieldDescriptor
FindFieldByNumber(tagNumber int32) *FieldDescriptor
FindFieldByJSONName(jsonName string) *FieldDescriptor
FieldDescriptor
是對 message 內 field(字段)的描述。
常用的方法:
GetName() string
GetFullyQualifiedName() string
AsEnumDescriptorProto() *descriptor.EnumDescriptorProto
String() string
GetValues() []*EnumValueDescriptor // 獲取該枚舉所有的枚舉值
FindValueByName(name string) *EnumValueDescriptor
FindValueByNumber(num int32) *EnumValueDescriptor
例子
demo1:打印出 proto 文件內所有的 message
// test.proto
syntax = "proto3";
package test;
enum Sex{
Man = 0;
Woman = 1;
}
message Player{
int64 userId = 1;
string name = 2;
Sex sex = 3;
repeated int64 friends = 4;
}
message Monster{
// 怪物等級
int32 level = 1;
}
import (
"fmt"
"github.com/jhump/protoreflect/desc/protoparse"
)
func main() {
var parser protoparse.Parser
fileDescriptors, _ := parser.ParseFiles("./test.proto")
// 因為只有一個文件,所以肯定只有一個 fileDescriptor
fileDescriptor := fileDescriptors[0]
for _, msgDescriptor := range fileDescriptor.GetMessageTypes() {
fmt.Println(msgDescriptor.GetName())
for _, fieldDesc := range msgDescriptor.GetFields() {
fmt.Println("\t", fieldDesc.GetType().String(), fieldDesc.GetName())
}
fmt.Println()
}
for _, enumDescriptor := range fileDescriptor.GetEnumTypes() {
fmt.Println(enumDescriptor.GetName())
for _, valueDescriptor := range enumDescriptor.GetValues() {
fmt.Println("\t", valueDescriptor.GetName())
}
fmt.Println()
}
}
// Output
Player
TYPE_INT64 userId
TYPE_STRING name
TYPE_ENUM sex
TYPE_INT64 friends
Monster
TYPE_INT32 level
Sex
Man
Woman
注意:輸出的字段名首字母都是小寫的,例如userId
,name
,這是因為我在proto
文件中給他們定義時,首字母都是小寫的。(通過proto
文件最終生成的proto.go
文件字段名的首字母都是大寫的)
demo2:為 proto message 生成其對應的 json 形式
package main
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/protoparse"
)
func main() {
var parser protoparse.Parser
fileDescriptors, _ := parser.ParseFiles("./test.proto")
// 因為只有一個文件,所以肯定只有一個 fileDescriptor
fileDescriptor := fileDescriptors[0]
m := make(map[string]interface{})
for _, msgDescriptor := range fileDescriptor.GetMessageTypes() {
m[msgDescriptor.GetName()] = convertMessageToMap(msgDescriptor)
}
bs, _ := json.MarshalIndent(m, "", "\t")
fmt.Println(string(bs))
}
func convertMessageToMap(message *desc.MessageDescriptor) map[string]interface{} {
m := make(map[string]interface{})
for _, fieldDescriptor := range message.GetFields() {
fieldName := fieldDescriptor.GetName()
if fieldDescriptor.IsRepeated() {
// 如果是一個數組的話,就返回 nil 吧
m[fieldName] = nil
continue
}
switch fieldDescriptor.GetType() {
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
m[fieldName] = convertMessageToMap(fieldDescriptor.GetMessageType())
continue
}
m[fieldName] = fieldDescriptor.GetDefaultValue()
}
return m
}
// Output
{
"Monster": {
"level": 0
},
"Player": {
"friends": null,
"name": "",
"sex": 0,
"userId": 0
},
"Union": {
"captain": {
"friends": null,
"name": "",
"sex": 0,
"userId": 0
}
}
}