Jonathan Chan
Jonathan Chan

Reputation: 2389

Reading bytes into structs using reflection

I'm trying to write functions that will allow me to marshal/unmarshal simple structs into byte arrays. I've succeeded in writing Marshal, with help from the kind folks at #go-nuts, but I'm running into trouble writing Unmarshal.

// Unmarshal unpacks the binary data and stores it in the packet using
// reflection.
func Unmarshal(b []byte, t reflect.Type) (pkt interface{}, err error) {
    buf := bytes.NewBuffer(b)
    p := reflect.New(t)
    v := reflect.ValueOf(p)
    for i := 0; i < t.NumField(); i++ {
        f := v.Field(i)
        switch f.Kind() {
        case reflect.String:
            // length of string
            var l int16
            var e error
            e = binary.Read(buf, binary.BigEndian, &l)
            if e != nil {
                err = e
                return
            }

            // read length-of-string bytes from the buffer
            raw := make([]byte, l)
            _, e = buf.Read(raw)
            if e != nil {
                err = e
                return
            }

            // convert the bytes to a string
            f.SetString(bytes.NewBuffer(raw).String())
        default:
            e := binary.Read(buf, binary.BigEndian, f.Addr())
            if e != nil {
                err = e
                return
            }
        }
    }

    pkt = p
    return
}

The problem with the code above is that the call to f.Addr() near the end is apparently trying to get the address of an unaddressable value.

If there is an alternative solution, I would appreciate that as well. Either way, any help would be much appreciated.

Thanks!

Upvotes: 0

Views: 1860

Answers (3)

Jeremy Wall
Jeremy Wall

Reputation: 25237

I'm going to bet that the reason f.Addr() has the problem because it actually isn't addressable.

the reflect package Type object has a method that will tell you if the type is addressable called CanAddr(). Assuming the field is addressable if it's not a string is not always true. If the struct is not passed in as a pointer to a struct then it's fields won't be addressable. For more details about what is and isn't addressable see: http://weekly.golang.org/pkg/reflect/#Value.CanAddr which outlines the correct rules.

Essentially for your code to work I think you need to ensure you always call it with a pointer to a struct.

Upvotes: 0

Sonia
Sonia

Reputation: 28355

Working example with lots of assumptions and a trivial data format:

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

// example marshalled format.  lets say that marshalled data will have
// four bytes of a formatted floating point number followed by two more
// printable bytes.
type m42 []byte

// example struct we'd like to unmarshal into.
type packet struct {
    S string // exported fields required for reflection
    F float64
}

// example usage
func main() {
    var p packet
    if err := Unmarshal(m42("3.14Pi"), &p); err == nil {
        fmt.Println(p)
    } else {
        fmt.Println(err)
    }
}

func Unmarshal(data m42, structPtr interface{}) error {
    vp := reflect.ValueOf(structPtr)
    ve := vp.Elem() // settable struct Value
    vt := ve.Type() // type info for struct
    nStructFields := ve.NumField()
    for i := 0; i < nStructFields; i++ {
        fv := ve.Field(i) // settable field Value
        sf := vt.Field(i) // StructField type information
        // struct field name indicates which m42 field to unmarshal.
        switch sf.Name {
        case "S":
            fv.SetString(string(data[4:6]))
        case "F":
            s := string(data[0:4])
            if n, err := strconv.ParseFloat(s, 64); err == nil {
                fv.SetFloat(n)
            } else {
                return err
            }
        }
    }
    return nil
}

Appropriate alternative solutions would depend heavily on the real data you need to support.

Upvotes: 0

user811773
user811773

Reputation:

I think you should use

v := p.Elem()   // Get the value that 'p' points to

instead of

v := reflect.ValueOf(p)

Upvotes: 2

Related Questions