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

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
}

person Nishant Sankhala    schedule 11.09.2019    source источник


Ответы (1)


Если тип - интерфейс, с этим ничего не поделаешь. В фактическом значении это может быть структура или любой другой тип, реализующий этот интерфейс, но сам интерфейс type не может вам этого сказать, он не ограничивает конкретный тип.

Вы можете делать то, что хотите, если начнете с reflect.Value вместо _ 2_, потому что, если у вас есть значение, вы можете проверить значение (или его тип), которое хранится в интерфейсе. . Чтобы обернуть дескриптор 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: 
person icza    schedule 11.09.2019
comment
еще один связанный с этим вопрос: как получить значение с именем? Я не нашел хорошего ответа на то, как перебирать структуру pb. Благодарю. - person city; 06.08.2020
comment
@city Пожалуйста, отправьте новый вопрос со всеми подробностями. - person icza; 06.08.2020