使用反射将字节读入结构

时间:2012-03-17 07:32:17

标签: go

我正在尝试编写允许我将简单结构编组/解组成字节数组的函数。我已经成功地写了Marshal,在#go-nuts的善良人士的帮助下,但我在编写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
}

上面代码的问题在于,在结尾附近调用f.Addr()显然是在尝试获取无法寻址的值的地址。

如果有替代解决方案,我也会感激。无论哪种方式,任何帮助将非常感激。

谢谢!

3 个答案:

答案 0 :(得分:1)

我认为你应该使用

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

而不是

v := reflect.ValueOf(p)

答案 1 :(得分:0)

具有大量假设和简单数据格式的工作示例:

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
}

适当的替代解决方案在很大程度上取决于您需要支持的实际数据。

答案 2 :(得分:0)

我打算说f.Addr()之所以有问题是因为它实际上是不可寻址的。

反射包Type对象有一个方法可以告诉您类型是否可寻址,称为CanAddr()。假设字段是可寻址的,如果它不是字符串并不总是正确的。如果结构没有作为指向结构的指针传入,那么它的字段将不可寻址。有关可以和不可寻址的更多详细信息,请参阅:http://weekly.golang.org/pkg/reflect/#Value.CanAddr,其中概述了正确的规则。

基本上为了让你的代码工作,我认为你需要确保你总是用指向结构的指针来调用它。