0

Let's say I have a struct User

type User struct {
    Name     string `owm:"newNameFromAPI"`
}

The code below initialises the struct and passes it to a function

func main() {   
    dest := User{
        Name: "Sebastien",
    }
    
    deepUpdate(dest, "owm")
}

The function uses reflect in order to iterate over all the fields of the struct and update them accordingly (several chunks of code were removed for clarity)

func deepUpdate(destinationInterface interface{}, selector string) {

    // Here I'm not supposed to know that the type is `User`
    // And if I just use dest := destinationInterface, the value won't update
    dest := destinationInterface.(User)

    // Pointer to struct - addressable
    destVal := reflect.ValueOf(&dest)
    destType := reflect.TypeOf(&dest)
    // psindirect := reflect.Indirect(destVal) // ? ValueOf(<Ptr Value>) Requires to be adressed via reflect.Indirect() to access Field - https://stackoverflow.com/a/50098755/9077800

    // Struct
    destValElem := destVal.Elem()
    destTypeElem := destType.Elem()

    // Iterate over all fields of the struct
    for i := 0; i < destValElem.NumField(); i++ {
        // // for i := 0; i < destTypeElem.NumField(); i++ {
        destValField := destValElem.Field(i)
        destTypeField := destTypeElem.Field(i)

        switch destValField.Kind() {
        // Field is a struct
        case reflect.Struct:
            // Recursion
            fmt.Println("IS A STRUCT")
        default:
            // Get the complete tag
            tag := destTypeField.Tag
            if len(tag) == 0 {
                continue
            }

            // Get the value of our custom tag key
            tagVal := tag.Get(selector)
            if len(tagVal) == 0 {
                continue
            }

            // Access the value in our source data, thanks to a dot notation access - example: "user.profile.firstname"
            sourceValue := "John" // tee.Get(sourceInterface, tagVal)

            // Exported field
            f := destValField
            if f.IsValid() {
                // A Value can be changed only if it is
                // addressable and was not obtained by
                // the use of unexported struct fields.
                if f.CanSet() {
                    // Change value of Name
                    fv := reflect.ValueOf(sourceValue)
                    destValField.Set(fv)
                }
            }
            fmt.Println("NOT STRUCT")
        }
    }

    fmt.Println(dest.Name)
}

The problem is the following line, because I'm not supposed to know that the destinationInterface is to be casted to User.

How can I dynamically cast the interface to some unknown type defined at runtime, or any other way to get the same output of the updated Name "John" instead of "Sebastien"?

dest := destinationInterface.(User)

Here is the complete code running on the golang playgound

https://play.golang.org/p/sYvz-Fwp97P

aquiseb
  • 949
  • 8
  • 17
  • 3
    Pass `&dest` to the function – Burak Serdar Aug 11 '21 at 19:06
  • Always check the status of your type assertion (val, bool) , before proceeding with the rest of the logic (not relevant to your issue, but a good practice) – Inian Aug 11 '21 at 19:08
  • 2
    You cannot update the value unless you use a pointer, and you cannot assert a type at runtime. Pass a pointer and use `reflect` for the entirety of the function. (most functions that use reflection will error or panic if passed a non-pointer when one is needed https://play.golang.org/p/y0mRZeKab9R) – JimB Aug 11 '21 at 19:09

1 Answers1

1

You don't have to know the type of dest. The example is not recursive, but it can be easily upgraded.

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `owm:"newNameFromAPI"`
}

func main() {
    dest := User{
        Name: "Sebastien",
    }

    fmt.Println("Before:", dest.Name)
    deepUpdate(&dest, "owm")
    fmt.Println("After:", dest.Name)
}

func deepUpdate(dest interface{}, selector string) {
    //Get the reflect value of dest
    rv := reflect.ValueOf(dest)
    //Dereference every pointers
    for rv.Kind() == reflect.Ptr {
        rv = reflect.Indirect(rv)
    }

    //Check if its a struct, should use panic or return error
    if reflect.TypeOf(rv.Interface()).Kind() != reflect.Struct {
        fmt.Println("NOT A STRUCT")
        return
    }

    //Loop over the fields
    for i := 0; i < rv.NumField(); i++ {
        //Get the tag value
        tag := rv.Type().Field(i).Tag.Get(selector)
        if tag == "" {
            continue
        }

        //Get the source
        sourceValue := "John"

        //Assign the source to the dest's corresponding field
        if rv.Field(i).CanSet() {
            rv.Field(i).Set(reflect.ValueOf(sourceValue))
        }
    }
}

The only thing is that you have to use the same type for sourceValue that the corresponding field is.

Working example: https://goplay.space/#D0CmTaS5AiP

Fenistil
  • 3,734
  • 1
  • 27
  • 31