github.com/lxt1045/json@v0.0.0-20231013032136-54d6b1d6e525/struct_test.go (about) 1 package json 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "path/filepath" 7 "runtime" 8 "strconv" 9 "sync" 10 "testing" 11 12 "github.com/bytedance/sonic" 13 "github.com/lxt1045/json/testdata" 14 asrt "github.com/stretchr/testify/assert" 15 ) 16 17 func TestStruct(t *testing.T) { 18 type Anonymous struct { 19 Count int `json:"count"` 20 X string 21 } 22 fLine := func() string { 23 _, file, line, _ := runtime.Caller(1) 24 _, file = filepath.Split(file) 25 return file + ":" + strconv.Itoa(line) 26 } 27 idx := -1 28 29 datas := []struct { 30 name string 31 bs string 32 target string 33 data interface{} 34 }{ 35 { 36 name: "struct:" + fLine(), 37 bs: `{"out": -0.1 , "struct_0": [{ "count":8,"Anonymous":{"X":"xxx","Count":1}}]}`, 38 target: `{"out":-0.1,"struct_0":[{"count":8,"Anonymous":{"count":0,"X":"xxx"}}]}`, 39 data: &struct { 40 Out float32 `json:"out"` 41 Struct []struct { 42 Count int `json:"count"` 43 Anonymous Anonymous 44 } `json:"struct_0"` 45 }{}, 46 }, 47 { 48 name: "struct:" + fLine(), 49 bs: `{"out": 11 , "struct_0": [{ "count":8,"Anonymous":{"X":"xxx","Count":1}}]}`, 50 target: `{"out":11,"struct_0":[{"count":8,"Anonymous":{"count":0,"X":"xxx"}}]}`, 51 data: &struct { 52 Out int `json:"out"` 53 Struct []struct { 54 Count int `json:"count"` 55 Anonymous Anonymous 56 } `json:"struct_0"` 57 }{}, 58 }, 59 { 60 name: "interface:" + fLine(), 61 bs: `{"out":88,"struct_0":{"a":"<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>"}}`, 62 target: `{"out":88,"struct_0":{"a":"\u003ca href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\"\u003eTwitter for Mac\u003c/a\u003e"}}`, 63 data: &struct { 64 Out int `json:"out"` 65 Struct interface{} `json:"struct_0"` 66 }{}, 67 }, 68 { 69 name: "interface:" + fLine(), 70 bs: `{"out":"<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>"}`, 71 target: "{\"out\":\"\\u003ca href=\\\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\\\" rel=\\\"\\\\\\\"nofollow\\\\\\\"\\\"\\u003eTwitter for Mac\\u003c/a\\u003e\"}", 72 data: &struct { 73 Out string `json:"out"` 74 }{}, 75 }, 76 { 77 name: "interface:" + fLine(), 78 bs: `{"out": 11 , "struct_0": { "count":8}}`, 79 target: `{"out":11,"struct_0":{"count":8}}`, 80 data: &struct { 81 Out int `json:"out"` 82 Struct interface{} `json:"struct_0"` 83 }{}, 84 }, 85 { 86 name: "map" + fLine(), 87 bs: `{"out": 11 , "map_0": { "count":8,"y":"yyy"}}`, 88 target: `{"out":11,"map_0":{"count":8,"y":"yyy"}}`, 89 data: &map[string]interface{}{}, 90 }, 91 92 // 匿名类型; 指针匿名类型 93 { 94 name: "struct-Anonymous:" + fLine(), 95 bs: `{"out": 11 , "count":8,"X":"xxx"}`, 96 target: `{"out":11,"count":8,"X":"xxx"}`, 97 data: &struct { 98 Out int `json:"out"` 99 Anonymous 100 }{}, 101 }, 102 { 103 name: "struct:" + fLine(), 104 bs: `{"out": 11 , "struct_0": { "count":8}}`, 105 target: `{"out":11,"struct_0":{"count":8}}`, 106 data: &struct { 107 Out int `json:"out"` 108 Struct struct { 109 Count int `json:"count"` 110 } `json:"struct_0"` 111 }{}, 112 }, 113 { 114 name: "struct:" + fLine(), 115 bs: `{"out": 11 , "struct_0": { "count":8,"slice":[1,2,3]}}`, 116 target: `{"out":11,"struct_0":{"count":8,"slice":[1,2,3]}}`, 117 data: &struct { 118 Out int `json:"out"` 119 Struct struct { 120 Count int `json:"count"` 121 Slice []int `json:"slice"` 122 } `json:"struct_0"` 123 }{}, 124 }, 125 { 126 name: "slice:" + fLine(), 127 bs: `{"count":8 , "slice":[1,2,3] }`, 128 target: `{"count":8,"slice":[1,2,3]}`, 129 data: &struct { 130 Count int `json:"count"` 131 Slice []int `json:"slice"` 132 }{}, 133 }, 134 { 135 name: "bool:" + fLine(), 136 bs: `{"count":true , "false_0":false }`, 137 target: `{"count":true,"false_0":false}`, 138 data: &struct { 139 Count bool `json:"count"` 140 False bool `json:"false_0"` 141 }{}, 142 }, 143 { 144 name: "bool-ptr:" + fLine(), 145 bs: `{"count":true , "false_0":false }`, 146 target: `{"count":true,"false_0":false}`, 147 data: &struct { 148 Count *bool `json:"count"` 149 False *bool `json:"false_0"` 150 }{}, 151 }, 152 { 153 name: "bool-ptr-null:" + fLine(), 154 bs: `{"count":true , "false_0":null }`, 155 target: `{"count":true,"false_0":null}`, 156 data: &struct { 157 Count *bool `json:"count"` 158 False *bool `json:"false_0"` 159 }{}, 160 }, 161 { 162 name: "bool-ptr-empty:" + fLine(), 163 bs: `{"count":true }`, 164 target: `{"count":true,"false_0":null}`, 165 data: &struct { 166 Count *bool `json:"count"` 167 False *bool `json:"false_0"` 168 }{}, 169 }, 170 { 171 name: "float64:" + fLine(), 172 bs: `{"count":8.11 }`, 173 target: `{"count":8.11}`, 174 data: &struct { 175 Count float64 `json:"count"` 176 }{}, 177 }, 178 { 179 name: "float64-ptr:" + fLine(), 180 bs: `{"count":8.11 }`, 181 target: `{"count":8.11}`, 182 data: &struct { 183 Count *float64 `json:"count"` 184 }{}, 185 }, 186 { 187 name: "int-ptr:" + fLine(), 188 bs: `{"count":8 }`, 189 target: `{"count":8}`, 190 data: &struct { 191 Count *int `json:"count"` 192 }{}, 193 }, 194 { 195 name: "int:" + fLine(), 196 bs: `{"count":8 }`, 197 target: `{"count":8}`, 198 data: &struct { 199 Count int `json:"count"` 200 }{}, 201 }, 202 { 203 name: "string-ptr:" + fLine(), 204 bs: `{ "ZHCN":"chinese"}`, 205 target: `{"ZHCN":"chinese"}`, 206 data: &struct { 207 ZHCN *string 208 }{}, 209 }, 210 { 211 name: "string-notag:" + fLine(), 212 bs: `{ "ZHCN":"chinese"}`, 213 target: `{"ZHCN":"chinese"}`, 214 data: &struct { 215 ZHCN string 216 }{}, 217 }, 218 { 219 name: "string:" + fLine(), 220 bs: `{ "ZH_CN":"chinese", "ENUS":"English", "count":8 }`, 221 target: `{"ZH_CN":"chinese"}`, 222 data: &struct { 223 ZHCN string `json:"ZH_CN"` 224 }{}, 225 }, 226 } 227 if idx >= 0 { 228 datas = datas[idx : idx+1] 229 } 230 231 for i, d := range datas { 232 t.Run(d.name, func(t *testing.T) { 233 err := Unmarshal([]byte(d.bs), d.data) 234 if err != nil { 235 t.Fatalf("[%d]%s, error:%v\n", i, d.name, err) 236 } 237 bs, err := json.Marshal(d.data) 238 if err != nil { 239 t.Fatalf("i:%d, %s:%v\n", i, d.name, err) 240 } 241 if m, ok := (d.data).(*map[string]interface{}); ok { 242 var mm map[string]interface{} 243 json.Unmarshal([]byte(d.target), &mm) 244 for k, v := range *m { 245 asrt.Equalf(t, mm[k], v, fmt.Sprintf("i:%d,%s", i, d.name)) 246 } 247 for k, v := range mm { 248 asrt.Equalf(t, (*m)[k], v, fmt.Sprintf("i:%d,%s", i, d.name)) 249 } 250 251 t.Logf("\n%s\n%s", string(d.target), string(bs)) 252 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 253 } else if _, ok := (d.data).(*interface{}); ok { 254 t.Logf("\n%s\n%s", string(d.target), string(bs)) 255 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 256 } else { 257 asrt.Equalf(t, d.target, string(bs), fmt.Sprintf("i:%d,%s", i, d.name)) 258 } 259 260 runtime.GC() 261 _ = fmt.Sprintf("d :%+v", d) 262 }) 263 } 264 } 265 266 func TestStructMarshalInterface(t *testing.T) { 267 type st struct { 268 Count string `json:"count"` 269 X interface{} 270 } 271 s := &st{ 272 Count: "xxx", 273 X: st{ 274 Count: "xxx", 275 X: "xxx", 276 }, 277 } 278 bs, err := json.Marshal(&s) 279 if err != nil { 280 t.Fatal(err) 281 } 282 t.Logf("json:%s", string(bs)) 283 } 284 285 func TestStructMarshal(t *testing.T) { 286 type Anonymous struct { 287 Count int `json:"count"` 288 X string 289 } 290 fLine := func() string { 291 _, file, line, _ := runtime.Caller(1) 292 _, file = filepath.Split(file) 293 return file + ":" + strconv.Itoa(line) 294 } 295 idx := -5 296 297 datas := []struct { 298 name string 299 bs string 300 target string 301 target2 string 302 data interface{} 303 }{ 304 { 305 name: "struct:" + fLine(), 306 bs: `{"out": 11 , "struct_0": { "count":8}}`, 307 target: `{"out":11,"struct_0":{"count":8}}`, 308 data: &struct { 309 Out int `json:"out"` 310 Struct map[string]interface{} `json:"struct_0"` 311 }{}, 312 }, 313 { 314 name: "interface:" + fLine(), 315 bs: `{"out":88,"struct_0":{"a":"<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>"}}`, 316 target: `{"out":88,"struct_0":{"a":"\u003ca href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\"\u003eTwitter for Mac\u003c/a\u003e"}}`, 317 target2: `{"out":88,"struct_0":{"a":"<a href="//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22" rel="\"nofollow\"">Twitter for Mac</a>"}}`, 318 data: &struct { 319 Out int `json:"out"` 320 Struct interface{} `json:"struct_0"` 321 }{}, 322 }, 323 { 324 name: "interface:" + fLine(), 325 bs: `{"out":"<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>"}`, 326 target: "{\"out\":\"\\u003ca href=\\\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\\\" rel=\\\"\\\\\\\"nofollow\\\\\\\"\\\"\\u003eTwitter for Mac\\u003c/a\\u003e\"}", 327 target2: `{"out":"<a href="//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22" rel="\"nofollow\"">Twitter for Mac</a>"}`, 328 data: &struct { 329 Out string `json:"out"` 330 }{}, 331 }, 332 { 333 name: "interface:" + fLine(), 334 bs: `{"out": 11 , "struct_0": { "count":8}}`, 335 target: `{"out":11,"struct_0":{"count":8}}`, 336 data: &struct { 337 Out int `json:"out"` 338 Struct interface{} `json:"struct_0"` 339 }{}, 340 }, 341 { 342 name: "map" + fLine(), 343 bs: `{"out": 11 , "map_0": { "count":8,"y":"yyy"}}`, 344 target: `{"out":11,"map_0":{"count":8,"y":"yyy"}}`, 345 data: &map[string]interface{}{}, 346 }, 347 348 // 匿名类型; 指针匿名类型 349 { 350 name: "struct-Anonymous:" + fLine(), 351 bs: `{"out": 11 , "count":8,"X":"xxx"}`, 352 target: `{"out":11,"count":8,"X":"xxx"}`, 353 data: &struct { 354 Out int `json:"out"` 355 Anonymous 356 }{}, 357 }, 358 { 359 name: "struct:" + fLine(), 360 bs: `{"out": 11 , "struct_0": { "count":8}}`, 361 target: `{"out":11,"struct_0":{"count":8}}`, 362 data: &struct { 363 Out int `json:"out"` 364 Struct struct { 365 Count int `json:"count"` 366 } `json:"struct_0"` 367 }{}, 368 }, 369 { 370 name: "struct:" + fLine(), 371 bs: `{"out": 11 , "struct_0": { "count":8,"slice":[1,2,3]}}`, 372 target: `{"out":11,"struct_0":{"count":8,"slice":[1,2,3]}}`, 373 data: &struct { 374 Out int `json:"out"` 375 Struct struct { 376 Count int `json:"count"` 377 Slice []int `json:"slice"` 378 } `json:"struct_0"` 379 }{}, 380 }, 381 { 382 name: "slice:" + fLine(), 383 bs: `{"count":8 , "slice":[1,2,3] }`, 384 target: `{"count":8,"slice":[1,2,3]}`, 385 data: &struct { 386 Count int `json:"count"` 387 Slice []int `json:"slice"` 388 }{}, 389 }, 390 { 391 name: "bool:" + fLine(), 392 bs: `{"count":true , "false_0":false }`, 393 target: `{"count":true,"false_0":false}`, 394 data: &struct { 395 Count bool `json:"count"` 396 False bool `json:"false_0"` 397 }{}, 398 }, 399 { 400 name: "bool-ptr:" + fLine(), 401 bs: `{"count":true , "false_0":false }`, 402 target: `{"count":true,"false_0":false}`, 403 data: &struct { 404 Count *bool `json:"count"` 405 False *bool `json:"false_0"` 406 }{}, 407 }, 408 { 409 name: "bool-ptr-null:" + fLine(), 410 bs: `{"count":true , "false_0":null }`, 411 target: `{"count":true,"false_0":null}`, 412 data: &struct { 413 Count *bool `json:"count"` 414 False *bool `json:"false_0"` 415 }{}, 416 }, 417 { 418 name: "bool-ptr-empty:" + fLine(), 419 bs: `{"count":true }`, 420 target: `{"count":true,"false_0":null}`, 421 data: &struct { 422 Count *bool `json:"count"` 423 False *bool `json:"false_0"` 424 }{}, 425 }, 426 { 427 name: "float64:" + fLine(), 428 bs: `{"count":8.11 }`, 429 target: `{"count":8.11}`, 430 data: &struct { 431 Count float64 `json:"count"` 432 }{}, 433 }, 434 { 435 name: "float64-ptr:" + fLine(), 436 bs: `{"count":8.11 }`, 437 target: `{"count":8.11}`, 438 data: &struct { 439 Count *float64 `json:"count"` 440 }{}, 441 }, 442 { 443 name: "int-ptr:" + fLine(), 444 bs: `{"count":8 }`, 445 target: `{"count":8}`, 446 data: &struct { 447 Count *int `json:"count"` 448 }{}, 449 }, 450 { 451 name: "int:" + fLine(), 452 bs: `{"count":8 }`, 453 target: `{"count":8}`, 454 data: &struct { 455 Count int `json:"count"` 456 }{}, 457 }, 458 { 459 name: "string-ptr:" + fLine(), 460 bs: `{ "ZHCN":"chinese"}`, 461 target: `{"ZHCN":"chinese"}`, 462 data: &struct { 463 ZHCN *string 464 }{}, 465 }, 466 { 467 name: "string-notag:" + fLine(), 468 bs: `{ "ZHCN":"chinese"}`, 469 target: `{"ZHCN":"chinese"}`, 470 data: &struct { 471 ZHCN string 472 }{}, 473 }, 474 { 475 name: "string:" + fLine(), 476 bs: `{ "ZH_CN":"chinese", "ENUS":"English", "count":8 }`, 477 target: `{"ZH_CN":"chinese"}`, 478 data: &struct { 479 ZHCN string `json:"ZH_CN"` 480 }{}, 481 }, 482 } 483 if idx >= 0 { 484 datas = datas[idx : idx+1] 485 } 486 487 for i, d := range datas { 488 t.Run(d.name, func(t *testing.T) { 489 err := Unmarshal([]byte(d.bs), d.data) 490 if err != nil { 491 t.Fatalf("[%d]%s, error:%v\n", i, d.name, err) 492 } 493 bs, err := json.Marshal(d.data) 494 if err != nil { 495 t.Fatalf("i:%d, %s:%v\n", i, d.name, err) 496 } 497 if _, ok := (d.data).(*map[string]interface{}); ok { 498 t.Logf("\n%s\n%s", string(d.target), string(bs)) 499 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 500 } else if _, ok := (d.data).(*interface{}); ok { 501 t.Logf("\n%s\n%s", string(d.target), string(bs)) 502 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 503 } else { 504 asrt.Equalf(t, d.target, string(bs), fmt.Sprintf("i:%d,%s", i, d.name)) 505 } 506 507 runtime.GC() 508 _ = fmt.Sprintf("d :%+v", d) 509 510 bsOut, err := Marshal(d.data) 511 if err != nil { 512 t.Fatalf("i:%d, %s:%v\n", i, d.name, err) 513 } 514 if _, ok := (d.data).(*map[string]interface{}); ok { 515 t.Logf("\n%s\n%s", string(d.target), string(bs)) 516 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 517 } else if _, ok := (d.data).(*interface{}); ok { 518 t.Logf("\n%s\n%s", string(d.target), string(bs)) 519 // asrt.EqualValuesf(t, d.target, string(bs), d.name) 520 } else { 521 target := d.target2 522 if target == "" { 523 target = d.target 524 } 525 asrt.Equalf(t, target, string(bsOut), fmt.Sprintf("i:%d,%s", i, d.name)) 526 } 527 528 }) 529 } 530 } 531 532 func BenchmarkTestLoop(b *testing.B) { 533 loop := 10000000000 534 b.Run("loop1", func(b *testing.B) { 535 for i := 0; i < b.N; i++ { 536 for j := 0; j < loop; j++ { 537 } 538 } 539 }) 540 b.Run("loop1", func(b *testing.B) { 541 for i := 0; i < b.N; i++ { 542 for j := 0; j < loop; j++ { 543 } 544 } 545 }) 546 b.Run("loop1", func(b *testing.B) { 547 for i := 0; i < b.N; i++ { 548 for j := 0; j < loop; j++ { 549 } 550 for j := 0; j < loop; j++ { 551 } 552 } 553 }) 554 } 555 556 func TestStructST(t *testing.T) { 557 type ST struct { 558 Count int `json:"count"` 559 X string 560 ST *ST 561 } 562 st := ST{ 563 Count: 22, 564 X: "xxx", 565 ST: &ST{ 566 Count: 22, 567 X: "xxx", 568 }, 569 } 570 bs, err := json.Marshal(&st) 571 if err != nil { 572 t.Fatal(err) 573 } 574 t.Logf(string(bs)) 575 576 t.Run("qq", func(t *testing.T) { 577 // err := Unmarshal([]byte(d.bs), d.data) 578 // if err != nil { 579 // t.Fatalf("[%d]%s, error:%v\n", i, d.name, err) 580 // } 581 st2 := ST{} 582 err := json.Unmarshal(bs, &st2) 583 if err != nil { 584 t.Fatal(err) 585 } 586 bs, err := json.Marshal(&st2) 587 if err != nil { 588 t.Fatal(err) 589 } 590 t.Logf("%+v", string(bs)) 591 }) 592 593 t.Run("qq1", func(t *testing.T) { 594 st2 := ST{} 595 err := Unmarshal(bs, &st2) 596 if err != nil { 597 t.Fatal(err) 598 } 599 bs, err := json.Marshal(&st2) 600 if err != nil { 601 t.Fatal(err) 602 } 603 t.Logf("%+v", string(bs)) 604 }) 605 } 606 607 func BenchmarkParallelSafety(b *testing.B) { 608 bs := []byte(testdata.BookData) 609 str := string(bs) 610 d := testdata.Book{} 611 err := Unmarshal(bs, &d) 612 if err != nil { 613 b.Fatal(err) 614 } 615 sonic.UnmarshalString(str, &d) 616 617 var wg sync.WaitGroup 618 runtime.GC() 619 for x := 0; x < 100; x++ { 620 go func() { 621 wg.Add(1) 622 defer wg.Done() 623 runtime.Gosched() 624 for i := 0; i < b.N; i++ { 625 d := testdata.Book{} 626 _ = UnmarshalString(str, &d) 627 } 628 }() 629 } 630 b.Run("decode-lxt", func(b *testing.B) { 631 b.SetBytes(int64(len(bs))) 632 // b.ReportAllocs() 633 b.ResetTimer() 634 for i := 0; i < b.N; i++ { 635 d := testdata.Book{} 636 _ = UnmarshalString(str, &d) 637 } 638 }) 639 wg.Wait() 640 641 for x := 0; x < 100; x++ { 642 go func() { 643 wg.Add(1) 644 defer wg.Done() 645 runtime.Gosched() 646 for i := 0; i < b.N; i++ { 647 d := testdata.Book{} 648 _ = UnmarshalString(str, &d) 649 } 650 }() 651 } 652 runtime.GC() 653 b.Run("decode-parallel-lxt", func(b *testing.B) { 654 b.SetBytes(int64(len(bs))) 655 // b.ReportAllocs() 656 b.ResetTimer() 657 b.RunParallel(func(pb *testing.PB) { 658 for pb.Next() { 659 d := testdata.Book{} 660 _ = UnmarshalString(str, &d) 661 } 662 }) 663 664 }) 665 666 wg.Wait() 667 // encode 668 669 for x := 0; x < 100; x++ { 670 go func() { 671 wg.Add(1) 672 defer wg.Done() 673 runtime.Gosched() 674 for i := 0; i < b.N; i++ { 675 _, _ = Marshal(&d) 676 } 677 }() 678 } 679 runtime.GC() 680 b.Run("encode-lxt", func(b *testing.B) { 681 b.SetBytes(int64(len(bs))) 682 // b.ReportAllocs() 683 b.ResetTimer() 684 for i := 0; i < b.N; i++ { 685 _, _ = Marshal(&d) 686 } 687 }) 688 689 wg.Wait() 690 691 for x := 0; x < 100; x++ { 692 go func() { 693 wg.Add(1) 694 defer wg.Done() 695 for i := 0; i < b.N; i++ { 696 d := testdata.Book{} 697 _, _ = Marshal(&d) 698 } 699 }() 700 } 701 runtime.GC() 702 b.Run("encode-parallel-lxt", func(b *testing.B) { 703 b.SetBytes(int64(len(bs))) 704 // b.ReportAllocs() 705 b.ResetTimer() 706 b.RunParallel(func(pb *testing.PB) { 707 for pb.Next() { 708 _, _ = Marshal(&d) 709 } 710 }) 711 }) 712 }