Nested string fields can not be updated using reflection in an arbitrary Go struct

Issue

I’m trying to update all string fields in a struct and its subfields using reflection in golang for an arbitrary struct as follows:

package main
import (
    "fmt"
    "reflect"
    "strings"
)

func main() {
    type Inner struct {
        In1 string
        In2 []string
    }

    type Type struct {
        Name  string
        Names []string
        NewSt Inner
    }

    a := Type{
        Name:  " [ (Amir[ ",
        Names: nil,
        NewSt: Inner{
            In1: " [in1",
            In2: []string{"  [in2( "},
        },
    }

    trims(&a)
    fmt.Printf("%#v\n", a)
}

func trim(str string) string {
    return strings.TrimSpace(strings.Trim(str, "[](){}, "))
}

func trims(ps interface{}) {
    v := reflect.ValueOf(ps).Elem() // Elem() dereferences pointer
    for i := 0; i < v.NumField(); i++ {
        fv := v.Field(i)
        switch fv.Kind() {
        case reflect.String:
            fv.SetString(trim(fv.String()))
        case reflect.Struct:
            in := fv.Interface()
            trims(&in)
        }
    }
}

But I get panic: reflect: call of reflect.Value.Elem on struct Value error.
How can I fix it or is there any better way that I can do such thing??

Thanks.

Solution

func trims(ps interface{}) {
    v := reflect.ValueOf(ps)
    if v.Kind() == reflect.Ptr {
        v = v.Elem() // Elem() dereferences pointer
    }
    if v.Kind() != reflect.Struct {
        panic("not struct")
    }
    for i := 0; i < v.NumField(); i++ {
        fv := v.Field(i)
        switch fv.Kind() {
        case reflect.String:
            fv.SetString(trim(fv.String()))
        case reflect.Struct:
            // use Addr() to get an addressable
            // value of the field
            in := fv.Addr().Interface()

            // do not use &in, that evaluates
            // to *interface{}, that's almost
            // NEVER what you want
            trims(in)
        case reflect.Slice:
            if fv.Type().Elem().Kind() == reflect.String {
                for i := 0; i < fv.Len(); i++ {
                    fv.Index(i).SetString(trim(fv.Index(i).String()))
                }
            }
        }
    }
}

https://go.dev/play/p/JkJTJzTckNA

Answered By – mkopriva

Answer Checked By – Jay B. (GoLangFix Admin)

Leave a Reply

Your email address will not be published.