github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/jsoni/jsoni_test.go (about)

     1  package jsoni_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  	"testing"
     9  
    10  	"github.com/bingoohuang/gg/pkg/jsoni"
    11  	"github.com/bingoohuang/gg/pkg/jsoni/extra"
    12  	"github.com/bingoohuang/gg/pkg/randx"
    13  	"github.com/bingoohuang/gg/pkg/reflector"
    14  	"github.com/bingoohuang/gg/pkg/strcase"
    15  	"github.com/modern-go/reflect2"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  type MapStringBytes map[string][]byte
    20  
    21  type ContextKey int
    22  
    23  const (
    24  	Converting ContextKey = iota
    25  )
    26  
    27  func (m *MapStringBytes) UnmarshalJSONContext(ctx context.Context, data []byte) error {
    28  	api := ctx.Value(jsoni.ContextCfg).(jsoni.API)
    29  	if ctx.Value(Converting) == "go" {
    30  		var mm map[string]string
    31  		if err := api.Unmarshal(ctx, data, &mm); err != nil {
    32  			return err
    33  		}
    34  
    35  		*m = revertMap(mm)
    36  		return nil
    37  	}
    38  
    39  	r := make(map[string][]byte)
    40  	err := api.Unmarshal(ctx, data, &r)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	*m = r
    45  	return nil
    46  }
    47  
    48  func (m *MapStringBytes) MarshalJSONContext(ctx context.Context) ([]byte, error) {
    49  	api := ctx.Value(jsoni.ContextCfg).(jsoni.API)
    50  	if ctx.Value(Converting) == "go" {
    51  		return api.Marshal(ctx, convertMap(m))
    52  	}
    53  
    54  	return api.Marshal(ctx, (map[string][]byte)(*m))
    55  }
    56  
    57  func convertMap(m *MapStringBytes) map[string]string {
    58  	mm := make(map[string]string, len(*m))
    59  	for k, v := range *m {
    60  		mm[k] = string(v)
    61  	}
    62  	return mm
    63  }
    64  
    65  func revertMap(mm map[string]string) map[string][]byte {
    66  	r := make(map[string][]byte, len(mm))
    67  	for k, v := range mm {
    68  		r[k] = []byte(v)
    69  	}
    70  	return r
    71  }
    72  
    73  func TestContext(t *testing.T) {
    74  	m := make(MapStringBytes)
    75  	m["name"] = []byte("bingoohuang")
    76  	m["addr"] = []byte("中华人民共和国")
    77  
    78  	jmc := reflect.TypeOf((*jsoni.MarshalerContext)(nil)).Elem()
    79  	mt := reflect.ValueOf(m).Type()
    80  	assert.False(t, mt.Implements(jmc))
    81  	assert.False(t, mt.ConvertibleTo(jmc))
    82  	pmt := reflect.PtrTo(mt)
    83  	assert.True(t, pmt.Implements(jmc))
    84  	assert.True(t, pmt.ConvertibleTo(jmc))
    85  
    86  	api := jsoni.ConfigCompatibleWithStandardLibrary
    87  
    88  	bytes, _ := api.Marshal(nil, &m)
    89  	assert.Equal(t, `{"addr":"5Lit5Y2O5Lq65rCR5YWx5ZKM5Zu9","name":"YmluZ29vaHVhbmc="}`, string(bytes))
    90  
    91  	ctx := context.WithValue(context.Background(), Converting, "go")
    92  	bytes, _ = api.Marshal(ctx, &m)
    93  	assert.Equal(t, `{"addr":"中华人民共和国","name":"bingoohuang"}`, string(bytes))
    94  
    95  	bytes, _ = api.Marshal(nil, m)
    96  	assert.Equal(t, `{"addr":"5Lit5Y2O5Lq65rCR5YWx5ZKM5Zu9","name":"YmluZ29vaHVhbmc="}`, string(bytes))
    97  
    98  	bytes, _ = api.Marshal(ctx, m)
    99  	assert.Equal(t, `{"addr":"中华人民共和国","name":"bingoohuang"}`, string(bytes))
   100  
   101  	m = make(MapStringBytes)
   102  	assert.Nil(t, jsoni.Unmarshal([]byte(`{"addr":"5Lit5Y2O5Lq65rCR5YWx5ZKM5Zu9","name":"YmluZ29vaHVhbmc="}`), &m))
   103  	expect := MapStringBytes{"name": []byte("bingoohuang"), "addr": []byte("中华人民共和国")}
   104  	assert.Equal(t, expect, m)
   105  
   106  	assert.Nil(t, api.Unmarshal(ctx, []byte(`{"addr":"中华人民共和国","name":"bingoohuang"}`), &m))
   107  	assert.Equal(t, expect, m)
   108  }
   109  
   110  func TestMarshalClear(t *testing.T) {
   111  	f := struct {
   112  		Foo string `json:",clearQuotes"`
   113  	}{
   114  		Foo: `{"age":10}`,
   115  	}
   116  	s, _ := jsoni.MarshalToString(f)
   117  	assert.Equal(t, `{"Foo":{"age":10}}`, s)
   118  
   119  	a := struct {
   120  		Foo string `json:",clearQuotes"`
   121  	}{
   122  		Foo: `["age",10]`,
   123  	}
   124  	s, _ = jsoni.MarshalToString(a)
   125  	assert.Equal(t, `{"Foo":["age",10]}`, s)
   126  
   127  	b := struct {
   128  		Foo string `json:",clearQuotes"`
   129  	}{
   130  		Foo: `"age"`,
   131  	}
   132  	s, _ = jsoni.MarshalToString(b)
   133  	assert.Equal(t, `{"Foo":"\"age\""}`, s)
   134  }
   135  
   136  func TestMarshalJSONArray(t *testing.T) {
   137  	f := struct {
   138  		Foo []json.Number `json:",nilasempty"`
   139  	}{}
   140  
   141  	s, _ := jsoni.MarshalToString(f)
   142  	assert.Equal(t, `{"Foo":[]}`, s)
   143  }
   144  
   145  func TestMarshalJSON(t *testing.T) {
   146  	f := struct {
   147  		Foo json.Number
   148  	}{}
   149  
   150  	jsoni.Unmarshal([]byte(`{"Foo":"12345"}`), &f)
   151  	foo, _ := f.Foo.Int64()
   152  	assert.Equal(t, int64(12345), foo)
   153  
   154  	s, _ := jsoni.MarshalToString(f)
   155  	assert.Equal(t, `{"Foo":12345}`, s)
   156  }
   157  
   158  func TestMarshalJSONTag(t *testing.T) {
   159  	f := struct {
   160  		Foo json.Number `json:"foo"`
   161  	}{}
   162  
   163  	jsoni.Unmarshal([]byte(`{"foo":"12345"}`), &f)
   164  	foo, _ := f.Foo.Int64()
   165  	assert.Equal(t, int64(12345), foo)
   166  
   167  	s, _ := jsoni.MarshalToString(f)
   168  	assert.Equal(t, `{"foo":12345}`, s)
   169  }
   170  
   171  func TestInt64(t *testing.T) {
   172  	f := struct {
   173  		Foo int64
   174  	}{}
   175  
   176  	c := jsoni.Config{EscapeHTML: true, Int64AsString: true}.Froze()
   177  	c.RegisterExtension(&extra.NamingStrategyExtension{Translate: strcase.ToCamelLower})
   178  
   179  	ctx := context.Background()
   180  	c.Unmarshal(ctx, []byte(`{"Foo":"12341"}`), &f)
   181  	assert.Equal(t, int64(12341), f.Foo)
   182  	c.Unmarshal(ctx, []byte(`{"Foo":12342}`), &f)
   183  	assert.Equal(t, int64(12342), f.Foo)
   184  	c.Unmarshal(ctx, []byte(`{"foo":12343}`), &f)
   185  	assert.Equal(t, int64(12343), f.Foo)
   186  	c.Unmarshal(ctx, []byte(`{"foo":"12344"}`), &f)
   187  	assert.Equal(t, int64(12344), f.Foo)
   188  
   189  	s, _ := c.MarshalToString(ctx, f)
   190  	assert.Equal(t, `{"foo":"12344"}`, s)
   191  }
   192  
   193  func TestUInt64(t *testing.T) {
   194  	f := struct {
   195  		Foo uint64
   196  	}{}
   197  
   198  	c := jsoni.Config{EscapeHTML: true, Int64AsString: true}.Froze()
   199  	c.RegisterExtension(&extra.NamingStrategyExtension{Translate: extra.LowerCaseWithUnderscores})
   200  	ctx := context.Background()
   201  	c.Unmarshal(ctx, []byte(`{"Foo":"12341"}`), &f)
   202  	assert.Equal(t, uint64(12341), f.Foo)
   203  	c.Unmarshal(ctx, []byte(`{"Foo":12342}`), &f)
   204  	assert.Equal(t, uint64(12342), f.Foo)
   205  	c.Unmarshal(ctx, []byte(`{"foo":12343}`), &f)
   206  	assert.Equal(t, uint64(12343), f.Foo)
   207  	c.Unmarshal(ctx, []byte(`{"foo":"12344"}`), &f)
   208  	assert.Equal(t, uint64(12344), f.Foo)
   209  
   210  	s, _ := c.MarshalToString(ctx, f)
   211  	assert.Equal(t, `{"foo":"12344"}`, s)
   212  }
   213  
   214  type structFieldDecoder struct {
   215  	field        reflect2.StructField
   216  	fieldDecoder jsoni.ValDecoder
   217  }
   218  
   219  var (
   220  	hashes    []int64
   221  	hashMap   = make(map[int64]*structFieldDecoder)
   222  	switchMap = &tenFieldsStructDecoder{}
   223  )
   224  
   225  func init() {
   226  	obj := reflector.New(switchMap)
   227  	for i := 1; i <= 10; i++ {
   228  		hash := randx.Int64()
   229  		hashes = append(hashes, hash)
   230  		d := &structFieldDecoder{}
   231  		hashMap[hash] = d
   232  		obj.Field(fmt.Sprintf("H%d", i)).Set(hash)
   233  		obj.Field(fmt.Sprintf("D%d", i)).Set(d)
   234  
   235  	}
   236  }
   237  
   238  /*
   239  🕙[2021-08-15 09:51:09.043] ❯ go test -bench=.
   240  goos: darwin
   241  goarch: amd64
   242  pkg: github.com/bingoohuang/gg/pkg/jsoni
   243  cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
   244  BenchmarkMapHash-12       623845              1875 ns/op             560 B/op         38 allocs/op
   245  BenchmarkSwitch-12        661324              1760 ns/op             560 B/op         38 allocs/op
   246  */
   247  
   248  func BenchmarkMapHash(b *testing.B) {
   249  	b.ReportAllocs()
   250  	for n := 0; n < b.N; n++ {
   251  		randx.Shuffle(hashes)
   252  		for _, h := range hashes {
   253  			if _, ok := hashMap[h]; !ok {
   254  				b.Errorf("hash not found")
   255  			}
   256  		}
   257  	}
   258  }
   259  
   260  func BenchmarkSwitch(b *testing.B) {
   261  	b.ReportAllocs()
   262  	for n := 0; n < b.N; n++ {
   263  		randx.Shuffle(hashes)
   264  		for _, h := range hashes {
   265  			if d := switchMap.Switch(h); d == nil {
   266  				b.Errorf("hash not found")
   267  			}
   268  		}
   269  	}
   270  }
   271  
   272  type tenFieldsStructDecoder struct {
   273  	H1, H2, H3, H4, H5, H6, H7, H8, H9, H10 int64
   274  	D1, D2, D3, D4, D5, D6, D7, D8, D9, D10 *structFieldDecoder
   275  }
   276  
   277  func (d *tenFieldsStructDecoder) Switch(fieldHash int64) *structFieldDecoder {
   278  	switch fieldHash {
   279  	case d.H1:
   280  		return d.D1
   281  	case d.H2:
   282  		return d.D2
   283  	case d.H3:
   284  		return d.D3
   285  	case d.H4:
   286  		return d.D4
   287  	case d.H5:
   288  		return d.D5
   289  	case d.H6:
   290  		return d.D6
   291  	case d.H7:
   292  		return d.D7
   293  	case d.H8:
   294  		return d.D8
   295  	case d.H9:
   296  		return d.D9
   297  	case d.H10:
   298  		return d.D10
   299  	default:
   300  		return nil
   301  	}
   302  }