go中的动态结构体字段枚举和属性解析

问题描述 投票:0回答:1

我正在尝试为我的 go 程序创建自定义属性标签。这些标签将与字段一起使用,这些字段将从保管库中提取它们的值,例如

Password string \
金库:“密码”``。下面描述的函数应该遍历结构体的所有字段(包括嵌套结构体),并将所有标签记录在 TagParser.ParsedTagMap 中,并带有指向目标值的指针。 ParseTags 函数将接收 ServerConfiguration 结构,并填充一些值。

自定义结构如下所示:

type ServerConfiguration struct {
    Server1 EndpointConfiguration `yaml:"server1"`
    Server2 EndpointConfiguration `yaml:"server2"`
}

type EndpointConfiguration struct {
    Rest  RestEndpoint     `yaml:"rest"`
    Login LoginCredentials `yaml:"login"`
}

type LoginCredentials struct {
    Username string `vault:"keycloak_username"`
    Password string `vault:"keycloak_password"`
}

应该解析自定义结构的代码:

type TagParser struct {
    SourceStruct interface{}
    ParsedTagMap map[string]interface{}
}

func ParseTags(tagName string, data interface{}) map[string]interface{} {
    var tagParser TagParser
    tagParser.ParsedTagMap = make(map[string]interface{})
    tagParser.GetNestedTag(tagName, data)
    slog.Debug("TagParser[" + tagName + "]: Parsed " + strconv.Itoa(len(tagParser.ParsedTagMap)) + " tags")
    return tagParser.ParsedTagMap
}

func (tp *TagParser) GetNestedTag(tagName string, data interface{}) {
    val := reflect.ValueOf(data)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    if val.Kind() != reflect.Struct {
        return
    }

    for i := 0; i < val.NumField(); i++ {
        field := val.Field(i)

        tagValue := val.Type().Field(i).Tag.Get(tagName)
        if tagValue != "" {
            tp.ParsedTagMap[tagValue] = field.Interface()
        }
        if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
            tp.GetNestedTag(tagName, field.Interface())
        }
    }
}

提取字段后,一个单独的函数将调用vault REST API,并检索它们各自的值(带有值断言);

// var secret *vault.KVSecret retrieved before
for key, value := range vaultSecretMapping {
    secretValue, ok := secret.Data["data"].(map[string]interface{})[key].(string)
    if !ok {
        panic("Unable to parse " + key + " from vault")
    }
    switch p := value.(type) {
    case *[]byte:
        *p = []byte(secretValue)
    case *string:
        *p = secretValue
    default: // Should never happen
        panic("Unknown type for secret value")
    }
}

我需要解析函数是动态的(不仅仅是字符串),因为我将来必须使用其他数据类型(sych as []byte)。

我尝试摆弄 go 的反射库,但运气不佳。在

TagParser.ParsedTagMap
中,使用当前设置,我需要类似的东西:
{"password": *interface{metadata:{type: string}, data: {*"pointer to the proper ServerConfiguration nested struct"}}}

我可以手动实现这个结果:

TagParser.ParsedTagMap\["password"\]= &ServerConfiguration.Server1.LoginCredentials.Password

但是使用反射会混淆调试,并且 go 的按值传递取消引用reflect.Value 在函数之间传递(我认为)。调用 field.Addr() 会产生恐慌。

go reflection annotations tags
1个回答
0
投票

你可以这样做:

type TagParser struct {
    // 1. use [] to collect all fields that have the same key
    // 2. use reflect.Value so that you can utilize f.Set()
    tags map[string][]reflect.Value
}

func ParseTags(tag string, data interface{}) map[string][]reflect.Value {
    var p TagParser
    p.tags = make(map[string][]reflect.Value)
    p.parse(tag, reflect.ValueOf(data))
    return p.tags
}

func (p *TagParser) parse(tag string, rv reflect.Value) {
    if rv.Kind() == reflect.Ptr {
        rv = rv.Elem()
    }
    if rv.Kind() != reflect.Struct {
        return
    }

    rt := rv.Type()
    for i := 0; i < rv.NumField(); i++ {
        f := rv.Field(i)
        if k := rt.Field(i).Tag.Get(tag); k != "" {
            p.tags[k] = append(p.tags[k], f)
        }
        p.parse(tag, f)
    }
}

然后像这样使用它:

var cfg ServerConfiguration
tags := ParseTags("vault", &cfg)

vaultSecretMapping := map[string]any{
    // ..
}
for k, v := range vaultSecretMapping {
    if ff, ok := tags[k]; ok && len(ff) > 0 {
        for i := range ff {
            ff[i].Set(reflect.ValueOf(v))
        }
    }
}

https://go.dev/play/p/-lS262VUjpG

请注意,如果

f.Set
不可寻址或通过使用
unexported
结构字段获得,f 将会出现恐慌。

© www.soinside.com 2019 - 2024. All rights reserved.