Как получить все имена полей в сложных структурах, созданных 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: 
Другие вопросы по тегам