vmihailenco / msgpack

msgpack.org[Go] MessagePack encoding for Golang
https://msgpack.uptrace.dev/
BSD 2-Clause "Simplified" License
2.39k stars 236 forks source link

Extension throws error: Decode(nonaddressable main.MyDecimal) #324

Open schalekamp opened 2 years ago

schalekamp commented 2 years ago

When I try the run the following code I get the following error:

error encoding message with encoder msgpack: Decode(nonaddressable main.MyDecimal) encoded bytes: [131 204 204 204 188 205 1 44 207 0 0 1 119 10 189 34 219 204 208] error decoding message EOF decoded map: map[204:188 300:1610792903387]

package main

import (
    "bytes"
    "fmt"
    "github.com/shopspring/decimal"
    "github.com/vmihailenco/msgpack/v5"
)

func init() {
    msgpack.RegisterExt(4, (*MyDecimal)(nil))
}

type MyDecimal struct {
    decimal.Decimal
}

var (
    _ msgpack.Marshaler   = (*MyDecimal)(nil)
    _ msgpack.Unmarshaler = (*MyDecimal)(nil)
)

func (md *MyDecimal) MarshalMsgpack() ([]byte, error) {
    return []byte(md.String()), nil
}

func (md *MyDecimal) UnmarshalMsgpack(b []byte) error {
    dec, err := decimal.NewFromString(string(b))
    if err != nil {
        return err
    }
    md.Decimal = dec
    return nil
}

func test_mpack_extension() {

    var msg2 = make(map[int]interface{})
    msg2[204] = 188
    msg2[300] = 1610792903387
    d, _ := decimal.NewFromString("3.25")
    var md MyDecimal
    md.Decimal = d
    msg2[208] = md

    var bmsg bytes.Buffer
    enc2 := msgpack.NewEncoder(&bmsg)
    enc2.UseCompactInts(true)
    enc2.UseCompactFloats(true)
    err := enc2.Encode(msg2)
    if err != nil {
        fmt.Printf("error encoding message with encoder %v\n", err)
    }

    fmt.Printf("encoded bytes: %v \n", bmsg.Bytes())

    var v2 map[int]interface{}
    err = msgpack.Unmarshal(bmsg.Bytes(), &v2)
    if err != nil {
        fmt.Printf("error decoding message %v\n", err)
    }
    fmt.Printf("decoded map: %v \n", v2)
}

func main() {
    test_mpack_extension()
}
vmihailenco commented 2 years ago

If you can, change the code to msg2[208] = &md. Proper fix requires changes to the library.

schalekamp commented 2 years ago

this seems to work btw



func decimalDecoder(dec *msgpack.Decoder, v reflect.Value, extLen int) error {
    rdr := dec.Buffered()
    data := make([]byte, extLen)
    rdr.Read(data)
    d, err := decimal.NewFromString(string(data))
    if err != nil {
        return err
    }
    if d_ptr, ok := v.Addr().Interface().(*decimal.Decimal); ok {
        *d_ptr = d
        return nil
    } else {
        return errors.New("decoding msgpack decimal value to wrong type")
    }
}

func decimalEncoder(enc *msgpack.Encoder, v reflect.Value) ([]byte, error) {
    if d, ok := v.Interface().(decimal.Decimal); ok {
        s := d.String()
        return []byte(s), nil
    } else {
        return nil, errors.New("could not convert value to *decimal.Decimal")
    }
}

func init() {
    msgpack.RegisterExtDecoder(4, decimal.Decimal{}, decimalDecoder)
    msgpack.RegisterExtEncoder(4, decimal.Decimal{}, decimalEncoder)
}
vmihailenco commented 2 years ago

Yes, you got the right idea. Ideally, the library should handle such situations better automatically...

schalekamp commented 2 years ago

I agree. whenever you have time for that ofcourse. Meanwhile thank you for writing and maintaining the library. It is great.

id01 commented 1 year ago

For those of you who are still having issues with this, I used this function. Essentially, I made all the encoding into non-pointer functions and made an "asymmetric ext registration". It's a workaround that works, I suppose, but you don't get that nice clean everything-is-a-pointer declaration.

func RegisterAsymmetricExt(extID int8, marshalType msgpack.Marshaler, unmarshalType msgpack.Unmarshaler) {
    msgpack.RegisterExtEncoder(extID, marshalType, func(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
        marshaler := v.Interface().(msgpack.Marshaler)
        return marshaler.MarshalMsgpack()
    })
    msgpack.RegisterExtDecoder(extID, unmarshalType, func(d *msgpack.Decoder, v reflect.Value, extLen int) error {
        b := make([]byte, extLen)
        err := d.ReadFull(b)
//      b, err := d.readN(extLen)
        if err != nil {
            return err
        }
        return v.Interface().(msgpack.Unmarshaler).UnmarshalMsgpack(b)
    })
}

func init() {
    RegisterAsymmetricExt(int8(VECTOR4_TYPE_CODE), Vector4{}, (*Vector4)(nil))
}

func (v Vector4) MarshalMsgpack() ([]byte, error) {
    buf := new(bytes.Buffer)
    err := binary.Write(buf, binary.BigEndian, v)
    if err != nil {
        return nil, err
    }
    return buf.Bytes(), err
}

func (v *Vector4) UnmarshalMsgpack(b []byte) error {
    if len(b) != 16 {
        return fmt.Errorf("invalid data length: got %d, wanted 16", len(b))
    }
    r := bytes.NewReader(b)
    return binary.Read(r, binary.BigEndian, v)
}
giautm commented 11 months ago

this seems to work btw

Thank you, I suggest another solution that will work on other types too.

// RegisterBinaryExt registers a binary marshaler/unmarshaler for the given type.
// The type must be implemented both interfaces.
func RegisterBinaryExt[T encoding.BinaryMarshaler, _ interface {
    encoding.BinaryUnmarshaler
    *T
}](extID int8) {
    var zero T
    msgpack.RegisterExtEncoder(extID, zero, func(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
        m := v.Interface().(encoding.BinaryMarshaler)
        return m.MarshalBinary()
    })
    msgpack.RegisterExtDecoder(extID, zero, func(d *msgpack.Decoder, v reflect.Value, extLen int) error {
        u := v.Addr().Interface().(encoding.BinaryUnmarshaler)
        b := make([]byte, extLen)
        if err := d.ReadFull(b); err != nil {
            return err
        }
        return u.UnmarshalBinary(b)
    })
}

func init() {
    RegisterBinaryExt[decimal.Decimal](1)
}