Dans le cadre du développement de gaum, je me suis retrouvé avec le besoin de décomposer une instance d’un struct pour alimenter un appel à Scan avec les attributs de l’instance mappés sur les colonnes de la ligne sql.
Lorsqu’on manipule un struct dans le code, c’est-à-dire :
// BaseType is a poorly name type that exemplifies a struct to be embedded
type BaseType struct {
aField int64
}
type OtherType struct {
BaseType
anotherField string
}Maintenant, dans votre code, vous devriez pouvoir simplement invoquer aField sur une instance de OtherType en le référençant directement ainsi :
var example OtherType
example.aFieldMalheureusement, la reflection ne raisonne pas comme ça ; lorsque vous faites de la reflection sur le type OtherType, vous verrez deux champs :
anotherFieldde typestring- et un champ
Anonymousde typeBaseType
Maintenant, si vous pensez que vous pouvez déclarer OtherType de cette manière :
example := OtherType{
BaseType: BaseType{
aField: 1,
},
anotherField: "a vaue",
}Et le référencer en appelant example.BaseType.aField, cela a effectivement du sens.
Alors, pour mon cas où j’essayais de mapper un ensemble de noms de colonnes sql sur des champs de struct, j’ai fini par écrire une fonction récursive de déballage qui construit une map des noms de champs sérialisés sql vers les champs de struct ; si une clé s’avère répétée, le champ du struct est écarté car, dans l’usage courant, les champs les plus externes masquent ceux embarqués à l’intérieur.
func unwrapEmbedded(fields map[string]reflect.StructField, anonfield *reflect.StructField) {
tod := anonfield.Type
embeddedFields := []*reflect.StructField{}
var ok bool
for fieldIndex := 0; fieldIndex < tod.NumField(); fieldIndex++ {
field := tod.Field(fieldIndex)
if field.Anonymous {
embeddedFields = append(embeddedFields, &field)
continue
}
name := nameFromTagOrName(field)
// the assumption that are no conflicting fields is made, if there were conflicting fields
// the user will most likely get a complain about ambiguous identifier before this or
// upon scanning next, it is too risky to try to recreate what the compiler would do.
_, ok = fields[name]
if !ok {
fields[name] = field
}
}
if len(embeddedFields) != 0 {
for _, v := range embeddedFields {
unwrapEmbedded(fields, v)
}
}
}Une dernière chose qui m’a piégé en essayant de corriger ceci, c’est que j’utilisais cette map pour ensuite récupérer les champs par index et que j’ai dû passer à le faire par nom, car procéder ainsi permet une récupération transparente des attributs comme dans la syntaxe normale de go.
J’ai donc dû changer
fVal dans ce contexte est un reflect.StructField issu de la map ci-dessus
vod dans ce contexte est la reflect.Value d’une instance du type que nous avons déballé.
fieldRecipients[i] = vod.FieldByIndex(fVal.Index).Addr().Interface()devient
fieldRecipients[i] = vod.FieldByName(fVal.Name).Addr().Interface()