From 16bb25fdaeaef2f4606ce330b1bad5b646763d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Sun, 27 Sep 2026 09:24:38 +0300 Subject: [PATCH] feat: allow opting out of text marshaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- decode.go | 11 +++++++++++ decode_value.go | 11 ++++++++++- encode.go | 11 +++++++++++ encode_value.go | 9 +++++++++ msgpack_test.go | 38 +++++++++++++++++++++++++++++++++++++- 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/decode.go b/decode.go index 20494cc0..22aa58e0 100644 --- a/decode.go +++ b/decode.go @@ -24,6 +24,7 @@ const ( disallowUnknownFieldsFlag usePreallocateValues disableAllocLimitFlag + disableTextUnmarshalerFlag ) type bufReader interface { @@ -223,6 +224,16 @@ func (d *Decoder) UseInternedStrings(on bool) { } } +// UseTextUnmarshaler controls whether encoding.TextUnmarshaler is used for +// values that implement it. It is enabled by default for compatibility. +func (d *Decoder) UseTextUnmarshaler(on bool) { + if on { + d.flags &^= disableTextUnmarshalerFlag + } else { + d.flags |= disableTextUnmarshalerFlag + } +} + // SetInternedStringsDictCap sets an initial capacity hint for the // interned-string dict, avoiding slice growth as entries are appended. // n is clamped to [0, maxDictLen]; 0 restores lazy allocation. diff --git a/decode_value.go b/decode_value.go index fecb72ee..dc7dc008 100644 --- a/decode_value.go +++ b/decode_value.go @@ -274,7 +274,7 @@ func unmarshalBinaryOrTextValue(d *Decoder, v reflect.Value) error { if err != nil { return err } - if msgpcode.IsString(c) { + if msgpcode.IsString(c) && d.flags&disableTextUnmarshalerFlag == 0 { return unmarshalTextValue(d, v) } return unmarshalBinaryValue(d, v) @@ -291,6 +291,15 @@ func unmarshalBinaryValue(d *Decoder, v reflect.Value) error { } func unmarshalTextValue(d *Decoder, v reflect.Value) error { + if d.flags&disableTextUnmarshalerFlag != 0 { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return valueDecoders[v.Elem().Kind()](d, v.Elem()) + } + return valueDecoders[v.Kind()](d, v) + } data, err := d.DecodeBytes() if err != nil { return err diff --git a/encode.go b/encode.go index 0e87555c..fd4cd56a 100644 --- a/encode.go +++ b/encode.go @@ -16,6 +16,7 @@ const ( useCompactFloatsFlag useInternedStringsFlag omitEmptyFlag + disableTextMarshalerFlag ) type writer interface { @@ -253,6 +254,16 @@ func (e *Encoder) UseInternedStrings(on bool) { } } +// UseTextMarshaler controls whether encoding.TextMarshaler is used for values +// that implement it. It is enabled by default for compatibility. +func (e *Encoder) UseTextMarshaler(on bool) { + if on { + e.flags &^= disableTextMarshalerFlag + } else { + e.flags |= disableTextMarshalerFlag + } +} + // SetInternedStringsDictCap sets an initial capacity hint for the // interned-string dict, avoiding map rehashing as entries are added. // n is clamped to [0, maxDictLen]; 0 restores lazy allocation. diff --git a/encode_value.go b/encode_value.go index 681dc691..ebc0b8f2 100644 --- a/encode_value.go +++ b/encode_value.go @@ -230,6 +230,9 @@ func marshalBinaryValue(e *Encoder, v reflect.Value) error { //------------------------------------------------------------------------------ func marshalTextValueAddr(e *Encoder, v reflect.Value) error { + if e.flags&disableTextMarshalerFlag != 0 { + return valueEncoders[v.Kind()](e, v) + } return marshalTextValue(e, ensureAddr(v)) } @@ -237,6 +240,12 @@ func marshalTextValue(e *Encoder, v reflect.Value) error { if nilable(v.Kind()) && v.IsNil() { return e.EncodeNil() } + if e.flags&disableTextMarshalerFlag != 0 { + if v.Kind() == reflect.Ptr { + return valueEncoders[v.Elem().Kind()](e, v.Elem()) + } + return valueEncoders[v.Kind()](e, v) + } marshaler := v.Interface().(encoding.TextMarshaler) data, err := marshaler.MarshalText() diff --git a/msgpack_test.go b/msgpack_test.go index 80cd564f..0f3f770d 100644 --- a/msgpack_test.go +++ b/msgpack_test.go @@ -12,15 +12,29 @@ import ( "testing" "time" + "github.com/Basekick-Labs/msgpack/v6" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/Basekick-Labs/msgpack/v6" ) type nameStruct struct { Name string } +type textMarshalerStruct struct { + Name string + Number int +} + +func (s *textMarshalerStruct) MarshalText() ([]byte, error) { + return []byte(s.Name), nil +} + +func (s *textMarshalerStruct) UnmarshalText(data []byte) error { + s.Name = string(data) + return nil +} + type MsgpackTest struct { suite.Suite @@ -39,6 +53,28 @@ func TestMsgpackTestSuite(t *testing.T) { suite.Run(t, new(MsgpackTest)) } +func (t *MsgpackTest) TestTextMarshalerCanBeDisabled() { + in := textMarshalerStruct{Name: "alice", Number: 42} + + data, err := msgpack.Marshal(in) + t.Require().NoError(err) + var textOut textMarshalerStruct + t.Require().NoError(msgpack.Unmarshal(data, &textOut)) + t.Equal("alice", textOut.Name) + t.Zero(textOut.Number) + + var encoded bytes.Buffer + enc := msgpack.NewEncoder(&encoded) + enc.UseTextMarshaler(false) + t.Require().NoError(enc.Encode(in)) + + var out textMarshalerStruct + dec := msgpack.NewDecoder(bytes.NewReader(encoded.Bytes())) + dec.UseTextUnmarshaler(false) + t.Require().NoError(dec.Decode(&out)) + t.Equal(in, out) +} + func (t *MsgpackTest) TestDecodeNil() { t.NotNil(t.dec.Decode(nil)) }