From c0ebe92c76ed8452c299819b4e5272afbbff3bd4 Mon Sep 17 00:00:00 2001 From: Sai to Yeung Date: Tue, 5 May 2020 00:09:56 -0400 Subject: [PATCH 1/2] Map keys of custom types should serialize using MarshalText when available --- misc_tests/jsoniter_map_test.go | 17 +++++++++++++++++ reflect_map.go | 24 +++++++++++++----------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/misc_tests/jsoniter_map_test.go b/misc_tests/jsoniter_map_test.go index b73de698..9075b603 100644 --- a/misc_tests/jsoniter_map_test.go +++ b/misc_tests/jsoniter_map_test.go @@ -50,3 +50,20 @@ func Test_encode_nil_map(t *testing.T) { should.NoError(err) should.Equal(`null`, output) } + +func Test_WhenMapKeyIsCustomType_AndImplementsTextMarshaler_MarshalTextIsUsed(t *testing.T) { + should := require.New(t) + input := map[customKey]string{customKey(1): "bar"} + jsoniterOutput, err := jsoniter.Marshal(input) + encodingJSONOutput, err := json.Marshal(input) + + should.NoError(err) + should.Equal(encodingJSONOutput, jsoniterOutput) + should.Equal(`{"foo":"bar"}`, string(jsoniterOutput)) +} + +type customKey int32 + +func (c customKey) MarshalText() ([]byte, error) { + return []byte("foo"), nil +} diff --git a/reflect_map.go b/reflect_map.go index 9e2b623f..8770f6e0 100644 --- a/reflect_map.go +++ b/reflect_map.go @@ -103,6 +103,19 @@ func encoderOfMapKey(ctx *ctx, typ reflect2.Type) ValEncoder { return encoder } } + + if typ == textMarshalerType { + return &directTextMarshalerEncoder{ + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + } + } + if typ.Implements(textMarshalerType) { + return &textMarshalerEncoder{ + valType: typ, + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + } + } + switch typ.Kind() { case reflect.String: return encoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String)) @@ -117,17 +130,6 @@ func encoderOfMapKey(ctx *ctx, typ reflect2.Type) ValEncoder { typ = reflect2.DefaultTypeOfKind(typ.Kind()) return &numericMapKeyEncoder{encoderOfType(ctx, typ)} default: - if typ == textMarshalerType { - return &directTextMarshalerEncoder{ - stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), - } - } - if typ.Implements(textMarshalerType) { - return &textMarshalerEncoder{ - valType: typ, - stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), - } - } if typ.Kind() == reflect.Interface { return &dynamicMapKeyEncoder{ctx, typ} } From a275d409465add08e0b8903ee66ff6c314b66cdb Mon Sep 17 00:00:00 2001 From: Sai to Yeung Date: Wed, 6 May 2020 07:42:37 -0400 Subject: [PATCH 2/2] Enable unmarshaling of custom map keys - this brings unmarshaling behavior in line with encoding/json - in general, any types that implement the interfaces from the encoding package (TextUnmarshaler, TextMarshaler, etc.) should use the provided method when available - this commit also moves test cases from jsoniter_map_test.go to value/tests/map_test.go --- misc_tests/jsoniter_map_test.go | 17 ----------- reflect_map.go | 52 +++++++++++++++++---------------- value_tests/map_test.go | 15 ++++++++++ 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/misc_tests/jsoniter_map_test.go b/misc_tests/jsoniter_map_test.go index 9075b603..b73de698 100644 --- a/misc_tests/jsoniter_map_test.go +++ b/misc_tests/jsoniter_map_test.go @@ -50,20 +50,3 @@ func Test_encode_nil_map(t *testing.T) { should.NoError(err) should.Equal(`null`, output) } - -func Test_WhenMapKeyIsCustomType_AndImplementsTextMarshaler_MarshalTextIsUsed(t *testing.T) { - should := require.New(t) - input := map[customKey]string{customKey(1): "bar"} - jsoniterOutput, err := jsoniter.Marshal(input) - encodingJSONOutput, err := json.Marshal(input) - - should.NoError(err) - should.Equal(encodingJSONOutput, jsoniterOutput) - should.Equal(`{"foo":"bar"}`, string(jsoniterOutput)) -} - -type customKey int32 - -func (c customKey) MarshalText() ([]byte, error) { - return []byte("foo"), nil -} diff --git a/reflect_map.go b/reflect_map.go index 8770f6e0..13fb67e3 100644 --- a/reflect_map.go +++ b/reflect_map.go @@ -49,6 +49,33 @@ func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder { return decoder } } + + ptrType := reflect2.PtrTo(typ) + if ptrType.Implements(unmarshalerType) { + return &referenceDecoder{ + &unmarshalerDecoder{ + valType: ptrType, + }, + } + } + if typ.Implements(unmarshalerType) { + return &unmarshalerDecoder{ + valType: typ, + } + } + if ptrType.Implements(textUnmarshalerType) { + return &referenceDecoder{ + &textUnmarshalerDecoder{ + valType: ptrType, + }, + } + } + if typ.Implements(textUnmarshalerType) { + return &textUnmarshalerDecoder{ + valType: typ, + } + } + switch typ.Kind() { case reflect.String: return decoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String)) @@ -63,31 +90,6 @@ func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder { typ = reflect2.DefaultTypeOfKind(typ.Kind()) return &numericMapKeyDecoder{decoderOfType(ctx, typ)} default: - ptrType := reflect2.PtrTo(typ) - if ptrType.Implements(unmarshalerType) { - return &referenceDecoder{ - &unmarshalerDecoder{ - valType: ptrType, - }, - } - } - if typ.Implements(unmarshalerType) { - return &unmarshalerDecoder{ - valType: typ, - } - } - if ptrType.Implements(textUnmarshalerType) { - return &referenceDecoder{ - &textUnmarshalerDecoder{ - valType: ptrType, - }, - } - } - if typ.Implements(textUnmarshalerType) { - return &textUnmarshalerDecoder{ - valType: typ, - } - } return &lazyErrorDecoder{err: fmt.Errorf("unsupported map key type: %v", typ)} } } diff --git a/value_tests/map_test.go b/value_tests/map_test.go index f8ffa5a3..02a1895a 100644 --- a/value_tests/map_test.go +++ b/value_tests/map_test.go @@ -31,6 +31,7 @@ func init() { map[string]*json.RawMessage{"hello": pRawMessage(json.RawMessage("[]"))}, map[Date]bool{{}: true}, map[Date2]bool{{}: true}, + map[customKey]string{customKey(1): "bar"}, ) unmarshalCases = append(unmarshalCases, unmarshalCase{ ptr: (*map[string]string)(nil), @@ -55,6 +56,9 @@ func init() { "2018-12-13": true, "2018-12-14": true }`, + }, unmarshalCase{ + ptr: (*map[customKey]string)(nil), + input: `{"foo": "bar"}`, }) } @@ -115,3 +119,14 @@ func (d Date2) UnmarshalJSON(b []byte) error { func (d Date2) MarshalJSON() ([]byte, error) { return []byte(d.Time.Format("2006-01-02")), nil } + +type customKey int32 + +func (c customKey) MarshalText() ([]byte, error) { + return []byte("foo"), nil +} + +func (c *customKey) UnmarshalText(value []byte) error { + *c = 1 + return nil +}