As part of developing gaum I found myself in the need to take apart an instance of a struct to populate a call to Scan with the instance’s attributes mapped to the sql row columns.
When addressing a struct in the code, ie:
// BaseType is a poorly name type that exemplifies a struct to be embedded
type BaseType struct {
aField int64
}
type OtherType struct {
BaseType
anotherField string
}
Now, in your code you should be able to simply invoke aField
in an instance of OtherType
by referencing it directly like so:
var example OtherType
example.aField
Sadly, reflection does not think like that, when reflecting over the tyle OtherType
you will see to fields:
anotherField
of typestring
- and an
Anonymous
field of typeBaseType
Now, if you think that you can declare OtherType
in this manner:
example := OtherType{
BaseType: BaseType{
aField: 1,
},
anotherField: "a vaue",
}
And reference it by calling example.BaseType.aField
it does make some sense.
So, for my case where I was trying to map a set of sql column names to struct fields I ended up making a recursive unwrapper function that makes a map of sql serialized field names to struct fields, if a key is found to be repeated the struct field is discarded because in the common use the outer most fields shadow the inner embedded ones.
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)
}
}
}
One last thing that bit me while trying to correct this is that I was using this map to then get the fields by index and had to switch to doing it by name because doing it this way makes seamless retrieval of attributes like in regular go syntax.
So I had to change
fVal
in this context is reflect.StructField
from the above map
vod
in this context is reflect.Value
of an instance of the type we unwrapped.
fieldRecipients[i] = vod.FieldByIndex(fVal.Index).Addr().Interface()
becomes
fieldRecipients[i] = vod.FieldByName(fVal.Name).Addr().Interface()