I'm trying to create a function that will let me set values on arbitrary structs dynamically by name. E.g. given a Page
with an embedded Link
struct, I want to be able to set page.Link.Url
to a new string.
Here's my code along with a failing test:
func TestSetFieldSubFieldByName(t *testing.T) {
page := APage {
"page title",
ALink {
"link title",
"http://www.example.com",
},
}
newPageTitle := "New page title"
setFieldSubFieldByName(&page, "Title", newPageTitle)
if page.Title != newPageTitle {
t.Errorf("Expected %s != %s", newPageTitle, page.Title)
}
newUrl := "http://new.example.com"
setFieldSubFieldByName(&page, "Link.Url", newUrl)
if page.Link.Url != newUrl {
t.Errorf("Expected %s != %s", newUrl, page.Link.Url)
}
}
type ALink struct {
Title string
Url string
}
type APage struct {
Title string
Link ALink
}
// Sets the value of a struct's field by name
func setFieldSubFieldByName(object interface{}, inputFieldName string, newValue string) {
log.Printf("Will try to set field '%s' to '%s' on %#v (type %T)", inputFieldName, newValue, object, object)
reflectedValues := reflect.ValueOf(object)
fieldNameParts := strings.Split(inputFieldName, ".")
fieldName := fieldNameParts[0]
// struct
reflectedElem := reflectedValues.Elem()
if reflectedElem.Kind() == reflect.Struct {
// exported field
field := reflectedElem.FieldByName(fieldName)
log.Printf("fieldByName returned %#v for field '%s'", field, fieldName)
if field.IsValid() {
// A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.
if field.CanSet() {
// change value of N
if field.Kind() == reflect.String {
field.SetString(newValue)
log.Printf("Field '%s' successfully updated to '%s'", fieldName, newValue)
} else if field.Kind() == reflect.Struct && len(fieldNameParts) > 0 {
log.Printf("Trying to set subfield '%s' to '%s'", fieldNameParts[1], newValue)
subObj := field.Interface()
setFieldSubFieldByName(subObj, fieldNameParts[1], newValue)
}
}
}
}
}
It successfully sets the value of page.Title
, but it's failing trying to set a value on the embedded struct. I think the issue is that I initially invoke the function passing a reference to page
, but when I recursively call setFieldSubFieldByName
it passes the actual object, not a pointer to it.
Is there a way of either getting a reference to the embedded struct before recursing, or is there some check I can perform at the start of the function to find out whether I'm looking at the actual instance or just a pointer?
How can I fix this?