Gary
Gary

Reputation: 2261

golang get a struct from an interface via reflection

Original question:

I'm trying to do some deserialization and I'm a little confused about how to access a struct when passing in an interface.

package main

import (
    "fmt"
    "reflect"
)

type Robot struct {
    Id int
}

func f(i interface{}) {
    v := reflect.ValueOf(i).Elem().FieldByName("Id")
    fmt.Println("fields: ", reflect.ValueOf(i).Elem().NumField()) 
    ptr := v.Addr().Interface().(*int)
    *ptr = 100
}

func main() {
    robot := Robot{}

    var iface interface{} = robot // if I omit this line and pass in robot this works
    f(&iface)
    fmt.Println(robot.Id) //I want to get here 100

}

http://play.golang.org/p/y6UN3KZxRB

The play example works if you just pass in the struct directly, however as it's possible for anything to be passed in that implements a specific interface (in my example case I'm just using the empty interface). However I can't figure out how to then treat it as a struct underneath.

Updated:

package main

import (
    "fmt"
    "reflect"
)

type MessageOne struct {
    Header   string `FixedWidth:0,4`
    FieldOne string `FixedWidth:"4,4"`
    FieldTwo string `FixedWidth:"8,4"`
}

type MessageTwo struct {
    FieldX string `FixedWidth:"X,Y"`
    FieldY string `FixedWidth:"X,Y"`
}

var (
    messageMap = map[string]interface{}{
        "msg1": MessageOne{FieldOne: "testValueUnchanged"},
        "msg2": MessageTwo{},
    }
)

func deserialize(input string, i interface{}) interface{} {
    value := reflect.ValueOf(i)
    fmt.Println("1st Value Type: ", value.Kind())
    // unswarp ptr
    value = value.Elem()
    fmt.Println("Unwrapped: ", value.Kind())
    value = value.Elem()
    fmt.Println("Unwrapped: ", value.Kind())

    // Create a copy that I can set?
    copyValue := reflect.New(value.Type()).Elem()
    fmt.Println("Orig Struct is settable", value.CanSet())
    fmt.Println("Orig StructField0 is settable", value.Field(0).CanSet())

    fmt.Println("Copy is: ", copyValue.Kind())
    fmt.Println("Copy Struct is settable", copyValue.CanSet())
    fmt.Println("Copy StructField0 is settable", copyValue.Field(0).CanSet())
    fmt.Println("Orig struct type is: ", value.Type())
    fmt.Println("Copy struct type is: ", copyValue.Type())

    copyValue.Field(1).SetString("testValueChanged")

    return copyValue.Interface()
}

func GetMessageFromInput(input string) interface{} {
    selector := input[0:4]
    fmt.Println(selector)
    field := messageMap[selector]
    return deserialize(input, &field)
}

func main() {
    val := messageMap["msg1"]

    serializedData := "msg1.012345678"

    deserializedVal := GetMessageFromInput(serializedData)

    //msg1 := deserializedVal.(MessageOne)

    fmt.Printf("Orig: %+v \nReceived: %+v", val, deserializedVal)
}

http://play.golang.org/p/Cj9oPPGSLM

I got the idea of copying my struct and thereby getting an addressable instance from here: https://gist.github.com/hvoecking/10772475

So I guess my question is now, is there a mechanism to access an addressable / settable struct without having to resort to a copy?

The underlying problem is taking strings (byte arrays really) and having a struct have the necessary info to effectively deserialize it without having to write a couple dozen deserialization functions which would suck to maintain. So the tags in those sample structs aren't addressed in the sample question, but accessing the structs tag fields would provide the offsets from which to populate the struct from the input bytes. Obviously I haven't gotten that far. Part of my frustration here is that it seems I've worked very hard to not get very far and I don't feel like i've learned much in the process.

Some additional play edits that got me my tags back: http://play.golang.org/p/2DbbWLDKPI

Upvotes: 11

Views: 31979

Answers (3)

J Korpics
J Korpics

Reputation: 59

In your original code, use:

var iface interface{} = &robot
f(iface)

Explanation
In the original version, we are sending in the address of the interface variable (which is a copy of the robot). This sends a pointer of type interface, and so reflect works on the copy of the robot.

var iface interface{} = robot
f(&iface)

What we need to do, is assign a pointer of type robot to the interface variable. Thus, when we send the interface we are sending the pointer of type robot, so reflect works with the actual robot object and not a copy.

var iface interface{} = &robot
f(iface)

Upvotes: 1

damick
damick

Reputation: 1160

You could use Type Assertion.

value, ok := i(Robot)
if ok {
    fmt.Println(value.Id)
}

From this stackoverflow post.

Upvotes: -1

Mr_Pink
Mr_Pink

Reputation: 109438

You don't want to pass a pointer to the interface, you want to pass in a pointer to your struct itself.

robot := &Robot{}
f(robot)

http://play.golang.org/p/owv-Y4dnkl

The moment you assigned robot to iface, you created a copy of the robot value. There's is no way to ever get a reference back to robot from iface.

When you pass in f(&iface), the call to reflect.ValueOf(i).Elem() is just returning the inner iface value, not a Robot struct value.

Upvotes: 5

Related Questions