10

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

Gary
  • 2,241
  • 3
  • 19
  • 21
  • You can use reflection to look up the names of members that exist on the structure and get access to the members. You don't actually get an instance of the structure back ever, since the structures being used are compiled in; instead you have to work with the set of interfaces to the structure properties provided by reflection. These give access to read and set functions which can be used to manipulate the underlying structure (as long as it is modifiable). – zstewart Dec 14 '15 at 17:21
  • That's essentially what I'm doing. The wrapping in an 'interface{}' is purely invented to mirror the problem I'm having in my code. I did actually find a work around, but the go library seems to exhibit this pattern of unwrapping an interface{} into a concrete type via interface. (https://golang.org/src/encoding/json/decode.go?s=2621:2669#L64) – Gary Dec 14 '15 at 17:29
  • This question is poorly worded and doesn't properly reflect the issue I'm having (although I thought it did). I'm working on recreating the problem more fully and reading the json decoding section as I go. Thanks for everyones input, I will post a new question when ready and put a link here. Thanks for everyone's input. – Gary Dec 15 '15 at 17:43

3 Answers3

5

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.

JimB
  • 104,193
  • 13
  • 262
  • 255
  • That doesn't answer the question. I want to know how to pass an interface in and access the underlying struct. – Gary Dec 14 '15 at 17:18
  • 1
    @Gary: you can't access the underlying struct if you don't pass in a pointer. Wrapping it in an interface again is superfluous, since it's put into an `interface{}` in the function call. – JimB Dec 14 '15 at 17:20
  • How does this work then: 74 func Unmarshal(data []byte, v interface{}) error { – Gary Dec 14 '15 at 17:23
  • @Gary, I'm guessing you're referring to the json package??, You get an error if you don't pass in a pointer: http://play.golang.org/p/55mMqnZt8c – JimB Dec 14 '15 at 17:27
  • That's probably the confusion, I am passing in a pointer. (&iface) (or at least it certainly looks that way) – Gary Dec 14 '15 at 17:32
  • 4
    @Gary, a pointer to an interface is mostly useless, and is almost always a mistake. A pointer to an interface doesn't give you a pointer to it's value; you can't modify a struct field without a pointer to that struct. – JimB Dec 14 '15 at 17:35
  • @Gary: maybe this will better explain your confusion with the json package: http://play.golang.org/p/R2bb71fFoL – JimB Dec 14 '15 at 18:09
  • I'll be digging into this, this answer and the below one are relevant. Thanks for the info. I'll spend some time wrapping my head around it. – Gary Dec 14 '15 at 18:40
1

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)
J Korpics
  • 59
  • 4
-1

You could use Type Assertion.

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

From this stackoverflow post.

damick
  • 1,055
  • 1
  • 10
  • 17