How to assign modified value of nested struct using reflection to struct - go

I have this structure
type Library struct {
Book Book
Owner Owner
Editorial Editorial
}
and I had to modify one of the tags of the auto-generated Book structure from category to categoryID.
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Category string `json:"category"`
}
My question is, after modifying those fields and getting correct structure of Book, how can I set it to become the new struct in Library.Book?
My code:
func renameTags(p any, m map[string]string) any {
rv := reflect.ValueOf(p)
re := rv.Elem()
rt := rv.Elem().Type()
fields := make([]reflect.StructField, rt.NumField())
for i := range fields {
f := rt.Field(i) // Book i == 0
if f.Type.Kind() == reflect.Struct && f.Type.Name() == "Book" {
fields2 := make([]reflect.StructField, f.Type.NumField()) // f == Book
for j := 0; j < f.Type.NumField(); j++ {
subField := f.Type.Field(j)
tag := subField.Tag.Get("json")
if v, ok := m[tag]; ok {
subField.Tag = reflect.StructTag(`json:"` + v + `"`)
}
fields2[j] = subField // change structure of Book
}
// here how to append new structure to f ?
//kk := reflect.StructOf(fields2).Elem()
}
fields[i] = f
}
st := reflect.StructOf(fields)
return rv.Convert(reflect.PtrTo(st)).Interface()
}
Any advice will be very much appreciated, thanks!

You can use reflect.New to create a new instance of the modified structure, then assign the value to the appropriate field in the original struct. Here's an updated code:
func renameTags(p any, m map[string]string) any {
rv := reflect.ValueOf(p)
re := rv.Elem()
rt := rv.Elem().Type()
fields := make([]reflect.StructField, rt.NumField())
for i := range fields {
f := rt.Field(i) // Book i == 0
if f.Type.Kind() == reflect.Struct && f.Type.Name() == "Book" {
fields2 := make([]reflect.StructField, f.Type.NumField()) // f == Book
for j := 0; j < f.Type.NumField(); j++ {
subField := f.Type.Field(j)
tag := subField.Tag.Get("json")
if v, ok := m[tag]; ok {
subField.Tag = reflect.StructTag(`json:"` + v + `"`)
}
fields2[j] = subField // change structure of Book
}
newStruct := reflect.New(reflect.StructOf(fields2))
re.Field(i).Set(newStruct)
}
fields[i] = f
}
st := reflect.StructOf(fields)
return rv.Convert(reflect.PtrTo(st)).Interface()
}

Related

How to set new tag value of nested struct using reflection

I have this structures:
type Library struct {
Book Book
Owner Owner
Editorial. Editorial
}
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Category string `json:"category"`
}
My code changes the json tag of the Book structure (autogenerated). How can I assign the new tag value ("categoryID") to the final structure, as in order to set new value I had to create a new variable "sub". How can I assign "sub" to f ?
func renameTags(p interface{}) any {
rv := reflect.ValueOf(p)
rt := rv.Elem().Type()
fields := make([]reflect.StructField, rt.NumField())
for i := range fields {
f := rt.Field(i)
if f.Type.Kind() == reflect.Struct && f.Type.Name() == "Book" {
sub := f.Type.Field(13)
sub.Tag = reflect.StructTag(`json:"` + "categoryID" + `"`)
}
fields[i] = f
}
st := reflect.ValueOf(fields)
x := st.Interface()
return x
}
Thanks in advance!

Golang reflect/iterate through interface{}

I’m looking to iterate through an interfaces keys.
Goal:
I want to implement a kind of middleware that checks for outgoing data (being marshalled to JSON) and edits nil slices to empty slices.
It should be agnostic/generic so that I don't need to specify field names. Ideally I can pass any struct as an interface and replace nil slices with empty slices.
Controller level
type Tag struct {
Name string
}
type BaseModel struct {
ID uuid.UUID
Active bool
}
type Model struct {
BaseModel // embedded struct
Name string
Number int
Tags []Tag
}
newModel, err := GetModel()
if err != nil {
...
}
RespondAsJson(w, newModel)
Middleware / Middle man json responder
It takes an interface to be generic/agnostic and reusable
in many different controllers
//(1) Attempting to use a map
func RespondWithJson(w http.ResponseWriter, data interface{}) {
obj, ok := data.(map[string]interface{})
// (1.1) OK == false
obj, ok := data.(map[interface{}]interface{})
// (1.2) OK == false
var newMap map[string]interface{}
bytes, _ := json.Marshal(&obj)
json.unMarshal(bytes, &newMap)
// (1.3) newMap has no underlying types on fields
// Nil slice of Tags went in, and it comes out as
// value=nil and type=interface{}
}
//(2) Skipping two as I believe this works, I'd like to avoid implementing it though.
//(3)
//(3.1)
RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
e := reflect.ValueOf(&data) // data = {*interface{} | Model}
if e.Kind() == reflect.Pointer {
e = e.Elem()
// e has a flag of 22, e.Elem() has a flag of 404
}
for i := 0; i < e.NumField(); i++ {
//PANIC: reflect: call of reflect.Value.NumField on interface Value
...
}
}
//(3.2)
// Reference: https://go.dev/blog/laws-of-reflection (third law)
RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
e := reflect.ValueOf(data) // data = {interface{} | Model}
if e.Kind() == reflect.Pointer {
e = e.Elem()
}
for i := 0; i < e.NumField(); i++ {
field := e.Field(i)
if field.Kind() == reflect.Slice && field.isNil() {
ok := field.CanSet() // OK == false
// Reference third law description in the reference above
valueOfField1 := reflect.ValueOf(&field)
ok := valueOfField1 .CanSet() // OK == false
...
valueOfField2 := reflect.ValueOf(field.Interface())
ok := valueOfField2.CanSet() // OK == false
...
}
}
}
//(3.3)
// Reference: (https://stackoverflow.com/questions/64211864/setting-nil-pointers-address-with-reflections) and others like it
RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
e := reflect.ValueOf(data) // {interface{} | Model}
if e.Kind() == reflect.Pointer { e = e.Elem() }
for i := 0; i < e.NumField(); i++ {
field := e.Field(i)
if field.Kind() == reflect.Slice && field.IsNil() {
tmp := reflect.New(field.Type())
if tmp.Kind() == reflect.Pointer { tmp = tmp.Elem()}
// (3.3.1)
ok := tmp.CanSet() // OK == true
tmp.Set(reflect.MakeSlice(field.Type(),0,0))
ok := field.CanSet()
// OK == false, tmp.set doesn't affect field value && can't set
field with value of tmp
// (3.3.2)
ok := tmp.Elem().CanSet()
// PANIC - call of reflect.value.Elem on Slicevalue
...
}
}
}
//(3.4)
// I can get it to work with passing &model to the function
// Once I'm inside the function, it's seen as an interface (or a
// *interface and the above is my results
RespondWithJson(w, &newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
e := reflect.ValueOf(data) // Data is {interface{} | *Model}
if e.Kind() == reflect.Pointer {
e = e.Elem()
// e has a flag of 22, e.Elem() has a flag of 409
}
for i := 0; i < e.NumField(); i++ {
field := e.Field(i)
if field.Kind() == reflect.Slice && field.IsNil() {
ok := field.CanSet()
// OK == true, field is addressable
if ok {
field.Set(reflect.MakeSlice(field.Type(), 0, 0))
// Success! Tags: nil turned into Tags: []
}
}
}
}
After that and many more.. random interations, I've found a way to make it work by passing memory address of struct to function which takes interface value.
If possible, I'd like to avoid the need to do this, as the function signature won't pick it up and it just leaves a small amount of room for error for other people on my team. I can of course just document the function, but its not bullet proof :)
Does anyone have suggestions for making this work without starting with a memory address to a struct? Can I set a field of an interface? ty very much!
In general, what you're probably looking for is something involving reflection. Your current code:
func someFunction(data interface{}) {
y := reflect.ValueOf(&data)
for i := 0; i < y.NumField(); i++ {
// PANIC: y is not a value of a struct
}
}
is pretty close, but it fails because data is a pointer. You can fix this by doing:
y := reflect.ValueOf(data)
if y.Kind() == reflect.Pointer {
y = y.Elem()
}
This will ensure that you have the actual value, and not a pointer to the value, allowing you to do NumField on it. Inside the loop, you check if the field is a slice and if it's nil and then set it to the value of a new instance of a slice of your field's type.
yField := y.Field(i)
if yField.Kind() == reflect.Slice && yField.IsNil() {
yField.Set(reflect.MakeSlice(yField.Elem().Type(), 0, 0)
}
Here we use Elem again because yField points to a slice, and so to create a new slice we need the inner type.
Finally, you need to add recursion to handle inner types if any of your fields are structs:
func SomeFunction(data interface{}) ([]byte, error) {
someFunctionInner(reflect.ValueOf(data))
return json.Marshal(data)
}
func someFunctionInner(v reflect.Value) {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
for i := 0; i < v.NumField(); i++ {
vField := v.Field(i)
switch vField.Kind() {
case reflect.Slice:
if vField.IsNil() {
vField.Set(reflect.MakeSlice(vField.Type(), 0, 0))
} else {
for j := 0; j < vField.Len(); j++ {
vFieldInner := vField.Index(j)
if vFieldInner.Kind() != reflect.Struct &&
(vFieldInner.Kind() != reflect.Pointer || vFieldInner.Elem().Kind() != reflect.Struct) {
continue
}
someFunctionInner(vFieldInner.Index(j))
}
}
case reflect.Pointer, reflect.Struct:
someFunctionInner(vField)
default:
}
}
}
and then you call it like this:
func main() {
m := Model{}
b, d := SomeFunction(&m)
fmt.Printf("Data: %+v\n", m)
fmt.Printf("JSON: %s, Error: %v\n", b, d)
}
Data: {BaseModel:{ID: Active:false} Name: Number:0 Tags:[]}
JSON: {"ID":"","Active":false,"Name":"","Number":0,"Tags":[]}, Error: <nil>
Note that I haven't added any sort of error-handling. Nor have I handled anything above regular pointers. Also, this function does expect a reference to an object because it is making modifications to said object. Finally, this code doesn't touch array logic at all. Still, this is likely what you're looking for.

Using reflect to assign a typed value

I'm working on one of our system applications, specifically in the configuration file handling bits. We currently have 3 different places where a configuration file can be stored, and that can possibly be extended later. What I'm trying to do is simplify the way we need to add a new managed field.
The solution I have so far looks something like this:
package main
import (
"reflect"
"strconv"
"strings"
)
type Datastore interface{}
type MyInt struct {
intVal int
}
func NewMyInt(key string, dv int, db *Datastore) *MyInt {
// Do something here to construct MyInt
return &MyInt{intVal: dv}
}
type Config struct {
myInts map[string]*MyInt
// Tag is of form "<key in DB>:<default value>"
Value1 MyInt "value1_key:12345"
Value2 MyInt "value2_key:54321"
}
func NewConfig(db *Datastore) *Config {
c := &Config{
myInts: make(map[string]*MyInt),
}
cType := reflect.TypeOf(c)
for i := 0; i < cType.NumField(); i++ {
f := cType.Field(i)
if f.Name == "myInts" {
continue
}
tag := string(f.Tag)
fields := strings.Split(tag, ":")
switch f.Type.Name() {
case "myInt":
intVal, _ := strconv.Atoi(fields[1])
val := NewMyInt(fields[0], intVal, db)
c.myInts[fields[0]] = val
// How do I set the i'th field to this newly constructed value?
}
}
return c
}
So far I'm just missing this piece to do the assignment.
For this question, you can try
func NewConfig(db *Datastore) *Config {
c := &Config{
myInts: make(map[string]*MyInt),
}
cType := reflect.TypeOf(c).Elem() // have to use Elem() to get actual value
cValue := reflect.ValueOf(c).Elem()
for i := 0; i < cType.NumField(); i++ {
f := cType.Field(i)
if f.Name == "myInts" {
continue
}
tag := string(f.Tag)
fields := strings.Split(tag, ":")
switch f.Type.Name() {
case "MyInt":
intVal, _ := strconv.Atoi(fields[1])
val := NewMyInt(fields[0], intVal, db)
c.myInts[fields[0]] = val
// How do I set the i'th field to this newly constructed value?
cValue.Field(i).Set(reflect.ValueOf(val).Elem())
}
}
fmt.Println(c.Value1.intVal, c.Value2.intVal)
return c
}

How to do a partial update methods for row

I'm trying to write a generic function to do a partial update for row, Here is what I got so far.
func GetSetByUpdateOptions(x interface{}) (string, error) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
if v.Kind() != reflect.Struct {
return "", errors.New("invalid_options_tag")
}
res := "SET "
for i := 0; i < v.NumField(); i++ {
setTemplate := "%s=%v, "
field := t.Field(i).Tag.Get("sql_set")
value := v.Field(i).Interface()
// NEED TO CHECK VALUE HERE
if field == "" {
return "", errors.New("invalid_options_tag")
}
res += fmt.Sprintf(setTemplate, field, value)
}
return res, nil
}
type Optional struct {
Field1 *int `set:"field1"` // might be nil if not set
Field2 *int `set:"field2"` // might be nil if not set
}
func UpdateRow(o Optional) { // here because i want a partial update. field1
s, _ := GetSetByUpdateOptions(o)
query := "UPDATE table SET status=" + s + " mtime=UNIX_TIMESTAMP(NOW()) WHERE id = ?"
// update
}
Just wondering how to define an input type since using an interface as input for GetSetByUpdateOptions seems not a good idea and How to filter some part of options because I want a partial update?

How do you set fields in a nested struct to their zero value?

Let's say I have an instance of struct Thing1 that I want to json.Marshal
type Thing1 struct {
A string `json:"a,omitempty"`
B int `json:"b,omitempty"`
C Thing2 `json:"c,omitempty"`
}
type Thing2 struct {
D bool `json:"d,omitempty"`
E int `json:"e,omitempty"`
}
...
thing1 := Thing1{
A: "test",
B: 42,
C: Thing2{D: true, E: 43},
}
How would you write a function that takes an instance of any struct and a list of fields to redact and returns a clone (or just mutates) of the incoming object, but with the redacted fields set to their zero values?
redact(thing1, []string{"B", "D"})
thing1 == Thing1{
A: "test",
B: 0,
C: Thing2{D: false, E: 43},
}
I can't use json:"-" as a field tag because the current ones in place are required for the query language I am using (Dgraph).
edit: not in the example, but objects inside arrays should also be redacted if applicable
Use reflect to manipulate the value of struct's field. Below is a proof of concept from what I have written in the comment. Since this is just a poc, you might need to adjust/modify the code to follow your needs.
This function mutate the original data. Code is self explanatory.
func redact(target interface{}, fieldsToModify []string) {
// if target is not pointer, then immediately return
// modifying struct's field requires addresable object
addrValue := reflect.ValueOf(target)
if addrValue.Kind() != reflect.Ptr {
return
}
// if target is not struct then immediatelly return
// this might need to be modified as per your needs
targetValue := addrValue.Elem()
targetType := targetValue.Type()
if targetType.Kind() != reflect.Struct {
return
}
// loop the fields
for i := 0; i < targetType.NumField(); i++ {
fType := targetType.Field(i)
fValue := targetValue.Field(i)
// if the field type is struct, then call redact() recursively
if fValue.Kind() == reflect.Struct {
redact(fValue.Addr().Interface(), fieldsToModify)
continue
}
// if the field is slice, loop then call redact() recursively
if fValue.Kind() == reflect.Array || fValue.Kind() == reflect.Slice {
for i := 0; i < fValue.Len(); i++ {
redact(fValue.Index(i).Addr().Interface(), fieldsToModify)
}
continue
}
// loop the fieldsToModify
for _, fieldToModify := range fieldsToModify {
if fieldToModify == fType.Name && fValue.CanSet() {
fValue.Set(reflect.Zero(fType.Type))
}
}
}
}
The redact() function pointer data in first parameter, since modifying fields require addresable object.
type Thing2 struct {
D bool `json:"d,omitempty"`
E int `json:"e,omitempty"`
}
type Thing1 struct {
A string `json:"a,omitempty"`
B int `json:"b,omitempty"`
C Thing2 `json:"c,omitempty"`
H []Thing2 `json:"h,omitempty"`
}
thing1 := Thing1{
A: "test",
B: 42,
C: Thing2{D: true, E: 43},
H: []Thing2{Thing2{D: true, E: 43}},
}
fmt.Printf("before: %#v \n", thing1)
// before: main.Thing1{A:"test", B:42, C:main.Thing2{D:true, E:43}, H:[]main.Thing2{main.Thing2{D:true, E:43}}}
redact(&thing1, []string{"B", "D"})
fmt.Printf("after: %#v \n", thing1)
// after: main.Thing1{A:"test", B:0, C:main.Thing2{D:false, E:43}, H:[]main.Thing2{main.Thing2{D:false, E:43}}}
Playground: https://play.golang.org/p/wy39DGdSVV7
Here's how to do it with the reflect package:
func redact(x interface{}, names []string) error {
// Starting value must be a pointer.
v := reflect.ValueOf(x)
if v.Kind() != reflect.Ptr {
return errors.New("not pointer")
}
// Create map for easy lookup.
m := make(map[string]bool)
for _, name := range names {
m[name] = true
}
redactValue(v, m)
return nil
}
func redactValue(v reflect.Value, names map[string]bool) {
switch v.Kind() {
case reflect.Ptr:
if v.IsZero() {
return
}
redactValue(v.Elem(), names)
case reflect.Interface:
if v.IsZero() {
return
}
iv := v.Elem()
switch iv.Kind() {
case reflect.Slice, reflect.Ptr:
redactValue(iv, names)
case reflect.Struct, reflect.Array:
// Copy required for modification.
copy := reflect.New(iv.Type()).Elem()
copy.Set(iv)
redactValue(copy, names)
v.Set(copy)
}
case reflect.Struct:
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
ft := sf.Type
fv := v.Field(i)
if names[sf.Name] {
// Clobber the field.
fv.Set(reflect.Zero(ft))
continue
}
redactValue(fv, names)
}
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
redactValue(v.Index(i), names)
}
}
}
Run it on the playground.
This answer handles structs, slices, arrays, pointers and interfaces.

Resources