Skip to content

Commit

Permalink
feat:(encoder) support concrete type implementing `encoding.TextMarsh…
Browse files Browse the repository at this point in the history
…aler` while encoding map
  • Loading branch information
AsterDY committed Dec 30, 2022
1 parent f421ee8 commit 1db182b
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 72 deletions.
60 changes: 49 additions & 11 deletions encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
package encoder

import (
`bytes`
`encoding/json`
`runtime`
`runtime/debug`
`strconv`
`sync`
`testing`
`time`

`github.com/bytedance/sonic/internal/rt`
`github.com/stretchr/testify/require`
"bytes"
"encoding"
"encoding/json"
"runtime"
"runtime/debug"
"strconv"
"sync"
"testing"
"time"

"github.com/bytedance/sonic/internal/rt"
"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -236,6 +237,14 @@ func (self *TextMarshalerImpl) MarshalText() ([]byte, error) {
return []byte(self.X), nil
}

type TextMarshalerImplV struct {
X string
}

func (self TextMarshalerImplV) MarshalText() ([]byte, error) {
return []byte(self.X), nil
}

type TextMarshalerStruct struct {
V TextMarshalerImpl
}
Expand All @@ -257,6 +266,35 @@ func TestEncoder_TextMarshaler(t *testing.T) {
require.Equal(t, `{"V":{"X":"{\"a\"}"}}`, string(ret3))
}

func TestTextMarshalTextKey_SortKeys(t *testing.T) {
v := map[*TextMarshalerImpl]string{
{"b"}: "b",
{"c"}: "c",
{"a"}: "a",
}
ret, err := Encode(v, SortMapKeys)
require.NoError(t, err)
require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret))

v2 := map[TextMarshalerImplV]string{
{"b"}: "b",
{"c"}: "c",
{"a"}: "a",
}
ret, err = Encode(v2, SortMapKeys)
require.NoError(t, err)
require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret))

v3 := map[encoding.TextMarshaler]string{
TextMarshalerImplV{"b"}: "b",
&TextMarshalerImpl{"c"}: "c",
TextMarshalerImplV{"a"}: "a",
}
ret, err = Encode(v3, SortMapKeys)
require.NoError(t, err)
require.Equal(t, `{"a":"a","b":"b","c":"c"}`, string(ret))
}

func TestEncoder_Marshal_EscapeHTML(t *testing.T) {
v := map[string]TextMarshalerImpl{"&&":{"<>"}}
ret, err := Encode(v, EscapeHTML)
Expand Down
19 changes: 19 additions & 0 deletions encoder/mapiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,29 @@ func (self *_MapIterator) appendGeneric(p *_MapPair, t *rt.GoType, v reflect.Kin
case reflect.Uint64 : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], *(*uint64)(k))]) ; return nil
case reflect.Uintptr : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uintptr)(k)))]) ; return nil
case reflect.Interface : return self.appendInterface(p, t, k)
case reflect.Struct, reflect.Pointer : return self.appendConcrete(p, t, k)
default : panic("unexpected map key type")
}
}

func (self *_MapIterator) appendConcrete(p *_MapPair, t *rt.GoType, k unsafe.Pointer) (err error) {
vt := t.Pack()
// if !vt.Implements(encodingTextMarshalerType) {
// panic("unexpected map key type")
// }
// compiler has already checked that the type implements the encoding.MarshalText interface
method, ok := vt.MethodByName("MarshalText")
if !ok {
panic("unexpected map key type")
}
rets := method.Func.Call([]reflect.Value{reflect.NewAt(vt, k).Elem()})
if err, ok := rets[1].Interface().(error); !ok && err != nil {
return err
}
p.k = rt.Mem2Str((rets[0].Bytes()))
return
}

func (self *_MapIterator) appendInterface(p *_MapPair, t *rt.GoType, k unsafe.Pointer) (err error) {
if len(rt.IfaceType(t).Methods) == 0 {
panic("unexpected map key type")
Expand Down
61 changes: 61 additions & 0 deletions issue_test/issue_recurse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package issue_test

import (
`encoding/json`
`fmt`
`reflect`
`strconv`
`testing`
`time`

`github.com/bytedance/sonic`
`github.com/davecgh/go-spew/spew`
`github.com/stretchr/testify/require`
)

func TestPointerValueRecurseMarshal(t *testing.T) {
info := &TestStruct1{
StartTime: JSONTime(time.Now()),
}
infos := &[]*TestStruct1{info}

bytes, err1 := json.Marshal(infos)
fmt.Printf("%+v\n", string(bytes))
spew.Dump(bytes, err1)

jbytes, err2 := sonic.Marshal(infos)
fmt.Printf("%+v\n", string(jbytes))
spew.Dump(jbytes, err2)
require.Equal(t, bytes, jbytes)
}

func TestPointerValueRecursePretouch(t *testing.T) {
info := &TestStruct2{
StartTime: JSONTime(time.Now()),
}
infos := &[]*TestStruct2{info}

bytes, err1 := json.Marshal(infos)
fmt.Printf("%+v\n", string(bytes))
spew.Dump(bytes, err1)

sonic.Pretouch(reflect.TypeOf(infos))
jbytes, err2 := sonic.Marshal(infos)
fmt.Printf("%+v\n", string(jbytes))
spew.Dump(jbytes, err2)
require.Equal(t, bytes, jbytes)
}

type TestStruct1 struct {
StartTime JSONTime
}

type TestStruct2 struct {
StartTime JSONTime
}

type JSONTime time.Time

func (t *JSONTime) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(time.Time(*t).Unix(), 10)), nil
}
61 changes: 0 additions & 61 deletions issue_test/issuex_test.go

This file was deleted.

0 comments on commit 1db182b

Please sign in to comment.