Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
schalekamp opened this issue Nov 7, 2021 · 6 comments
Open

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

schalekamp opened this issue Nov 7, 2021 · 6 comments

Comments

@schalekamp
Copy link

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
Copy link
Owner

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

@schalekamp
Copy link
Author

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
Copy link
Owner

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

@schalekamp
Copy link
Author

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

@id01
Copy link

id01 commented Sep 21, 2023

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
Copy link

giautm commented Nov 11, 2023

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)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants