使用反射将字节读入结构体

huangapple go评论82阅读模式
英文:

Reading bytes into structs using reflection

问题

我正在尝试编写函数,以便将简单的结构体编组/解组为字节数组。我已经成功编写了Marshal函数,在#go-nuts频道的热心人士的帮助下,但是在编写Unmarshal函数时遇到了问题。

上面的代码问题在于,在最后调用f.Addr()时,显然是在尝试获取一个不可寻址的值的地址。

如果有其他解决方案,我也会很感激。无论如何,任何帮助都将不胜感激。

谢谢!

英文:

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!

答案1

得分: 2

我认为你应该使用

v := p.Elem()   // 获取指针p指向的值

而不是

v := reflect.ValueOf(p)
英文:

I think you should use

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

instead of

v := reflect.ValueOf(p)

答案2

得分: 0

package main

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

type m42 []byte

type packet struct {
S string
F float64
}

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()
vt := ve.Type()
nStructFields := ve.NumField()
for i := 0; i < nStructFields; i++ {
fv := ve.Field(i)
sf := vt.Field(i)
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
}

英文:

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

package main

import (
    &quot;fmt&quot;
    &quot;reflect&quot;
    &quot;strconv&quot;
)

// 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&#39;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(&quot;3.14Pi&quot;), &amp;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 &lt; 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 &quot;S&quot;:
            fv.SetString(string(data[4:6]))
        case &quot;F&quot;:
            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.

答案3

得分: 0

我打赌f.Addr()出现问题的原因是它实际上不可寻址。

反射包的Type对象有一个方法可以告诉你类型是否可寻址,叫做CanAddr()。假设字段不是字符串,就认为它是可寻址的并不总是正确的。如果结构体没有作为指向结构体的指针传递进来,那么它的字段就不可寻址。关于可寻址和不可寻址的更多细节,请参考:http://weekly.golang.org/pkg/reflect/#Value.CanAddr,其中概述了正确的规则。

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

英文:

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.

huangapple
  • 本文由 发表于 2012年3月17日 15:32:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/9748196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定