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 }