Как получить все имена полей в сложных структурах, созданных golang proto
Я пытаюсь получить все имена полей в файле go, созданном из proto. Ниже представлена сгенерированная структура.
type Action struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Types that are valid to be assigned to ActionType:
// *Action_TaskAction
ActionType isAction_ActionType `protobuf_oneof:"action_type"`
}
Как видно, ActionType - это одно из полей в proto, которое реализовано, как показано ниже.
type isAction_ActionType interface {
isAction_ActionType()
}
type Action_TaskAction struct {
TaskAction *TaskAction `protobuf:"bytes,16,opt,name=task_action,json=taskAction,proto3,oneof"`
}
type TaskAction struct {
Progress float32 `protobuf:"fixed32,1,opt,name=progress,proto3" json:"progress,omitempty"`
}
Поскольку я хочу получить имя поля в структуре TaskAction, которое является Progress.
Я использую приведенный ниже код, чтобы получить имена полей, но сталкиваюсь с проблемой, если тип поля - интерфейс (для поля oneof)
func printFieldNames(t reflect.Type) error {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
printFieldNames(field.Type)
continue
}
if field.Type.Kind() == reflect.Interface {
// what to do here.
}
column := field.Tag.Get("json")
fmt.Println("column: ", column)
}
return nil
}
1 ответ
Если тип - интерфейс, с этим ничего не поделаешь. В фактическом значении это может быть структура или любой другой тип, реализующий этот интерфейс, но сам тип интерфейса не может сказать вам об этом, он не ограничивает конкретный тип.
Вы можете делать то, что хотите, если начнете с reflect.Value
вместо того reflect.Type
, потому что, если у вас есть значение, вы можете проверить значение (или его тип), которое хранится в интерфейсе. Чтобы получитьreflect.Value
дескриптор, заключенный в значение интерфейса, вы можете использовать reflect.Elem()
.
Кроме того, для обработки указателя на структуры вы снова можете использовать reflect.Elem()
чтобы получить указанное значение. Вы можете проверить, является ли значение указателем, сравнив его вид сreflect.Ptr
.
Вот пример вашего printFieldNames()
, переписан для работы с reflect.Value
, и он рекурсивно преобразуется в структуры, хранящиеся в значениях интерфейса. Это не решение, которое обрабатывает все случаи, но демонстрирует, как это сделать:
func printFieldNames(v reflect.Value) {
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.Kind() == reflect.Ptr {
field = field.Elem()
}
if field.Kind() == reflect.Struct {
printFieldNames(field)
continue
}
if field.Kind() == reflect.Interface {
wrapped := field.Elem()
if wrapped.Kind() == reflect.Ptr {
wrapped = wrapped.Elem()
}
printFieldNames(wrapped)
}
structfield := v.Type().Field(i)
column := structfield.Tag.Get("json")
fmt.Printf("column: %s, json tag: %s\n", structfield.Name, column)
}
}
Тестирование:
a := Action{
ActionType: Action_TaskAction{
TaskAction: &TaskAction{},
},
}
printFieldNames(reflect.ValueOf(a))
Результат будет (попробуйте на Go Playground):
column: Name, json tag: name,omitempty
column: Progress, json tag: progress,omitempty
column: ActionType, json tag: