github.com/seeker-insurance/kit@v0.0.13/jsonapi/embeded_structs_test.go (about) 1 package jsonapi 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "reflect" 7 "testing" 8 ) 9 10 func TestMergeNode(t *testing.T) { 11 parent := &Node{ 12 Type: "Good", 13 ID: "99", 14 Attributes: map[string]interface{}{"fizz": "buzz"}, 15 } 16 17 child := &Node{ 18 Type: "Better", 19 ClientID: "1111", 20 Attributes: map[string]interface{}{"timbuk": 2}, 21 } 22 23 expected := &Node{ 24 Type: "Better", 25 ID: "99", 26 ClientID: "1111", 27 Attributes: map[string]interface{}{"fizz": "buzz", "timbuk": 2}, 28 } 29 30 parent.merge(child) 31 32 if !reflect.DeepEqual(expected, parent) { 33 t.Errorf("Got %+v Expected %+v", parent, expected) 34 } 35 } 36 37 func TestIsEmbeddedStruct(t *testing.T) { 38 type foo struct{} 39 40 structType := reflect.TypeOf(foo{}) 41 stringType := reflect.TypeOf("") 42 if structType.Kind() != reflect.Struct { 43 t.Fatal("structType.Kind() is not a struct.") 44 } 45 if stringType.Kind() != reflect.String { 46 t.Fatal("stringType.Kind() is not a string.") 47 } 48 49 type test struct { 50 scenario string 51 input reflect.StructField 52 expectedRes bool 53 } 54 55 tests := []test{ 56 test{ 57 scenario: "success", 58 input: reflect.StructField{Anonymous: true, Type: structType}, 59 expectedRes: true, 60 }, 61 test{ 62 scenario: "wrong type", 63 input: reflect.StructField{Anonymous: true, Type: stringType}, 64 expectedRes: false, 65 }, 66 test{ 67 scenario: "not embedded", 68 input: reflect.StructField{Type: structType}, 69 expectedRes: false, 70 }, 71 } 72 73 for _, test := range tests { 74 res := isEmbeddedStruct(test.input) 75 if res != test.expectedRes { 76 t.Errorf( 77 "Scenario -> %s\nGot -> %v\nExpected -> %v\n", 78 test.scenario, res, test.expectedRes) 79 } 80 } 81 } 82 83 func TestShouldIgnoreField(t *testing.T) { 84 type test struct { 85 scenario string 86 input string 87 expectedRes bool 88 } 89 90 tests := []test{ 91 test{ 92 scenario: "opt-out", 93 input: annotationIgnore, 94 expectedRes: true, 95 }, 96 test{ 97 scenario: "no tag", 98 input: "", 99 expectedRes: false, 100 }, 101 test{ 102 scenario: "wrong tag", 103 input: "wrong,tag", 104 expectedRes: false, 105 }, 106 } 107 108 for _, test := range tests { 109 res := shouldIgnoreField(test.input) 110 if res != test.expectedRes { 111 t.Errorf( 112 "Scenario -> %s\nGot -> %v\nExpected -> %v\n", 113 test.scenario, res, test.expectedRes) 114 } 115 } 116 } 117 118 func TestIsValidEmbeddedStruct(t *testing.T) { 119 type foo struct{} 120 121 structType := reflect.TypeOf(foo{}) 122 stringType := reflect.TypeOf("") 123 if structType.Kind() != reflect.Struct { 124 t.Fatal("structType.Kind() is not a struct.") 125 } 126 if stringType.Kind() != reflect.String { 127 t.Fatal("stringType.Kind() is not a string.") 128 } 129 130 type test struct { 131 scenario string 132 input reflect.StructField 133 expectedRes bool 134 } 135 136 tests := []test{ 137 test{ 138 scenario: "success", 139 input: reflect.StructField{Anonymous: true, Type: structType}, 140 expectedRes: true, 141 }, 142 test{ 143 scenario: "opt-out", 144 input: reflect.StructField{ 145 Anonymous: true, 146 Tag: "jsonapi:\"-\"", 147 Type: structType, 148 }, 149 expectedRes: false, 150 }, 151 test{ 152 scenario: "wrong type", 153 input: reflect.StructField{Anonymous: true, Type: stringType}, 154 expectedRes: false, 155 }, 156 test{ 157 scenario: "not embedded", 158 input: reflect.StructField{Type: structType}, 159 expectedRes: false, 160 }, 161 } 162 163 for _, test := range tests { 164 res := (isEmbeddedStruct(test.input) && 165 !shouldIgnoreField(test.input.Tag.Get(annotationJSONAPI))) 166 if res != test.expectedRes { 167 t.Errorf( 168 "Scenario -> %s\nGot -> %v\nExpected -> %v\n", 169 test.scenario, res, test.expectedRes) 170 } 171 } 172 } 173 174 // TestEmbeddedUnmarshalOrder tests the behavior of the marshaler/unmarshaler of 175 // embedded structs when a struct has an embedded struct w/ competing 176 // attributes, the top-level attributes take precedence it compares the behavior 177 // against the standard json package 178 func TestEmbeddedUnmarshalOrder(t *testing.T) { 179 type Bar struct { 180 Name int `jsonapi:"attr,Name"` 181 } 182 183 type Foo struct { 184 Bar 185 ID string `jsonapi:"primary,foos"` 186 Name string `jsonapi:"attr,Name"` 187 } 188 189 f := &Foo{ 190 ID: "1", 191 Name: "foo", 192 Bar: Bar{ 193 Name: 5, 194 }, 195 } 196 197 // marshal f (Foo) using jsonapi marshaler 198 jsonAPIData := bytes.NewBuffer(nil) 199 if err := MarshalPayload(jsonAPIData, f); err != nil { 200 t.Fatal(err) 201 } 202 203 // marshal f (Foo) using json marshaler 204 jsonData, err := json.Marshal(f) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 // convert bytes to map[string]interface{} so that we can do a semantic JSON 210 // comparison 211 var jsonAPIVal, jsonVal map[string]interface{} 212 if err = json.Unmarshal(jsonAPIData.Bytes(), &jsonAPIVal); err != nil { 213 t.Fatal(err) 214 } 215 if err = json.Unmarshal(jsonData, &jsonVal); err != nil { 216 t.Fatal(err) 217 } 218 219 // get to the jsonapi attribute map 220 jDataMap, ok := jsonAPIVal["data"].(map[string]interface{}) 221 if !ok { 222 t.Fatal("Could not parse `data`") 223 } 224 jAttrMap, ok := jDataMap["attributes"].(map[string]interface{}) 225 if !ok { 226 t.Fatal("Could not parse `attributes`") 227 } 228 229 // compare 230 if !reflect.DeepEqual(jAttrMap["Name"], jsonVal["Name"]) { 231 t.Errorf("Got\n%s\nExpected\n%s\n", jAttrMap["Name"], jsonVal["Name"]) 232 } 233 } 234 235 // TestEmbeddedMarshalOrder tests the behavior of the marshaler/unmarshaler of 236 // embedded structs when a struct has an embedded struct w/ competing 237 // attributes, the top-level attributes take precedence it compares the 238 // behavior against the standard json package 239 func TestEmbeddedMarshalOrder(t *testing.T) { 240 type Bar struct { 241 Name int `jsonapi:"attr,Name"` 242 } 243 244 type Foo struct { 245 Bar 246 ID string `jsonapi:"primary,foos"` 247 Name string `jsonapi:"attr,Name"` 248 } 249 250 // get a jsonapi payload w/ Name attribute of an int type 251 payloadWithInt, err := json.Marshal(&OnePayload{ 252 Data: &Node{ 253 Type: "foos", 254 ID: "1", 255 Attributes: map[string]interface{}{ 256 "Name": 5, 257 }, 258 }, 259 }) 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 // get a jsonapi payload w/ Name attribute of an string type 265 payloadWithString, err := json.Marshal(&OnePayload{ 266 Data: &Node{ 267 Type: "foos", 268 ID: "1", 269 Attributes: map[string]interface{}{ 270 "Name": "foo", 271 }, 272 }, 273 }) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 // unmarshal payloadWithInt to f (Foo) using jsonapi unmarshaler; expecting an error 279 f := &Foo{} 280 if err = UnmarshalPayload(bytes.NewReader(payloadWithInt), f); err == nil { 281 t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error") 282 } 283 284 // unmarshal payloadWithString to f (Foo) using jsonapi unmarshaler; expecting no error 285 f = &Foo{} 286 if err = UnmarshalPayload(bytes.NewReader(payloadWithString), f); err != nil { 287 t.Error(err) 288 } 289 if f.Name != "foo" { 290 t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name) 291 } 292 293 // get a json payload w/ Name attribute of an int type 294 bWithInt, err := json.Marshal(map[string]interface{}{ 295 "Name": 5, 296 }) 297 if err != nil { 298 t.Fatal(err) 299 } 300 301 // get a json payload w/ Name attribute of an string type 302 bWithString, err := json.Marshal(map[string]interface{}{ 303 "Name": "foo", 304 }) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 // unmarshal bWithInt to f (Foo) using json unmarshaler; expecting an error 310 f = &Foo{} 311 if err := json.Unmarshal(bWithInt, f); err == nil { 312 t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error") 313 } 314 // unmarshal bWithString to f (Foo) using json unmarshaler; expecting no error 315 f = &Foo{} 316 if err := json.Unmarshal(bWithString, f); err != nil { 317 t.Error(err) 318 } 319 if f.Name != "foo" { 320 t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name) 321 } 322 } 323 324 func TestMarshalUnmarshalCompositeStruct(t *testing.T) { 325 type Thing struct { 326 ID int `jsonapi:"primary,things"` 327 Fizz string `jsonapi:"attr,fizz"` 328 Buzz int `jsonapi:"attr,buzz"` 329 } 330 331 type Model struct { 332 Thing 333 Foo string `jsonapi:"attr,foo"` 334 Bar string `jsonapi:"attr,bar"` 335 Bat string `jsonapi:"attr,bat"` 336 } 337 338 type test struct { 339 name string 340 payload *OnePayload 341 dst, expected interface{} 342 } 343 344 scenarios := []test{} 345 346 scenarios = append(scenarios, test{ 347 name: "Model embeds Thing, models have no annotation overlaps", 348 dst: &Model{}, 349 payload: &OnePayload{ 350 Data: &Node{ 351 Type: "things", 352 ID: "1", 353 Attributes: map[string]interface{}{ 354 "bar": "barry", 355 "bat": "batty", 356 "buzz": 99, 357 "fizz": "fizzy", 358 "foo": "fooey", 359 }, 360 }, 361 }, 362 expected: &Model{ 363 Foo: "fooey", 364 Bar: "barry", 365 Bat: "batty", 366 Thing: Thing{ 367 ID: 1, 368 Fizz: "fizzy", 369 Buzz: 99, 370 }, 371 }, 372 }) 373 374 { 375 type Model struct { 376 Thing 377 Foo string `jsonapi:"attr,foo"` 378 Bar string `jsonapi:"attr,bar"` 379 Bat string `jsonapi:"attr,bat"` 380 Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz 381 } 382 383 scenarios = append(scenarios, test{ 384 name: "Model embeds Thing, overlap Buzz attribute", 385 dst: &Model{}, 386 payload: &OnePayload{ 387 Data: &Node{ 388 Type: "things", 389 ID: "1", 390 Attributes: map[string]interface{}{ 391 "bar": "barry", 392 "bat": "batty", 393 "buzz": 99, 394 "fizz": "fizzy", 395 "foo": "fooey", 396 }, 397 }, 398 }, 399 expected: &Model{ 400 Foo: "fooey", 401 Bar: "barry", 402 Bat: "batty", 403 Buzz: 99, 404 Thing: Thing{ 405 ID: 1, 406 Fizz: "fizzy", 407 }, 408 }, 409 }) 410 } 411 412 { 413 type Model struct { 414 Thing 415 ModelID int `jsonapi:"primary,models"` //overrides Thing.ID due to primary annotation 416 Foo string `jsonapi:"attr,foo"` 417 Bar string `jsonapi:"attr,bar"` 418 Bat string `jsonapi:"attr,bat"` 419 Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz 420 } 421 422 scenarios = append(scenarios, test{ 423 name: "Model embeds Thing, attribute, and primary annotation overlap", 424 dst: &Model{}, 425 payload: &OnePayload{ 426 Data: &Node{ 427 Type: "models", 428 ID: "1", 429 Attributes: map[string]interface{}{ 430 "bar": "barry", 431 "bat": "batty", 432 "buzz": 99, 433 "fizz": "fizzy", 434 "foo": "fooey", 435 }, 436 }, 437 }, 438 expected: &Model{ 439 ModelID: 1, 440 Foo: "fooey", 441 Bar: "barry", 442 Bat: "batty", 443 Buzz: 99, 444 Thing: Thing{ 445 Fizz: "fizzy", 446 }, 447 }, 448 }) 449 } 450 451 { 452 type Model struct { 453 Thing `jsonapi:"-"` 454 ModelID int `jsonapi:"primary,models"` 455 Foo string `jsonapi:"attr,foo"` 456 Bar string `jsonapi:"attr,bar"` 457 Bat string `jsonapi:"attr,bat"` 458 Buzz int `jsonapi:"attr,buzz"` 459 } 460 461 scenarios = append(scenarios, test{ 462 name: "Model embeds Thing, but is annotated w/ ignore", 463 dst: &Model{}, 464 payload: &OnePayload{ 465 Data: &Node{ 466 Type: "models", 467 ID: "1", 468 Attributes: map[string]interface{}{ 469 "bar": "barry", 470 "bat": "batty", 471 "buzz": 99, 472 "foo": "fooey", 473 }, 474 }, 475 }, 476 expected: &Model{ 477 ModelID: 1, 478 Foo: "fooey", 479 Bar: "barry", 480 Bat: "batty", 481 Buzz: 99, 482 }, 483 }) 484 } 485 { 486 type Model struct { 487 *Thing 488 ModelID int `jsonapi:"primary,models"` 489 Foo string `jsonapi:"attr,foo"` 490 Bar string `jsonapi:"attr,bar"` 491 Bat string `jsonapi:"attr,bat"` 492 } 493 494 scenarios = append(scenarios, test{ 495 name: "Model embeds pointer of Thing; Thing is initialized in advance", 496 dst: &Model{Thing: &Thing{}}, 497 payload: &OnePayload{ 498 Data: &Node{ 499 Type: "models", 500 ID: "1", 501 Attributes: map[string]interface{}{ 502 "bar": "barry", 503 "bat": "batty", 504 "foo": "fooey", 505 "buzz": 99, 506 "fizz": "fizzy", 507 }, 508 }, 509 }, 510 expected: &Model{ 511 Thing: &Thing{ 512 Fizz: "fizzy", 513 Buzz: 99, 514 }, 515 ModelID: 1, 516 Foo: "fooey", 517 Bar: "barry", 518 Bat: "batty", 519 }, 520 }) 521 } 522 { 523 type Model struct { 524 *Thing 525 ModelID int `jsonapi:"primary,models"` 526 Foo string `jsonapi:"attr,foo"` 527 Bar string `jsonapi:"attr,bar"` 528 Bat string `jsonapi:"attr,bat"` 529 } 530 531 scenarios = append(scenarios, test{ 532 name: "Model embeds pointer of Thing; Thing is initialized w/ Unmarshal", 533 dst: &Model{}, 534 payload: &OnePayload{ 535 Data: &Node{ 536 Type: "models", 537 ID: "1", 538 Attributes: map[string]interface{}{ 539 "bar": "barry", 540 "bat": "batty", 541 "foo": "fooey", 542 "buzz": 99, 543 "fizz": "fizzy", 544 }, 545 }, 546 }, 547 expected: &Model{ 548 Thing: &Thing{ 549 Fizz: "fizzy", 550 Buzz: 99, 551 }, 552 ModelID: 1, 553 Foo: "fooey", 554 Bar: "barry", 555 Bat: "batty", 556 }, 557 }) 558 } 559 { 560 type Model struct { 561 *Thing 562 ModelID int `jsonapi:"primary,models"` 563 Foo string `jsonapi:"attr,foo"` 564 Bar string `jsonapi:"attr,bar"` 565 Bat string `jsonapi:"attr,bat"` 566 } 567 568 scenarios = append(scenarios, test{ 569 name: "Model embeds pointer of Thing; jsonapi model doesn't assign anything to Thing; *Thing is nil", 570 dst: &Model{}, 571 payload: &OnePayload{ 572 Data: &Node{ 573 Type: "models", 574 ID: "1", 575 Attributes: map[string]interface{}{ 576 "bar": "barry", 577 "bat": "batty", 578 "foo": "fooey", 579 }, 580 }, 581 }, 582 expected: &Model{ 583 ModelID: 1, 584 Foo: "fooey", 585 Bar: "barry", 586 Bat: "batty", 587 }, 588 }) 589 } 590 591 { 592 type Model struct { 593 *Thing 594 ModelID int `jsonapi:"primary,models"` 595 Foo string `jsonapi:"attr,foo"` 596 Bar string `jsonapi:"attr,bar"` 597 Bat string `jsonapi:"attr,bat"` 598 } 599 600 scenarios = append(scenarios, test{ 601 name: "Model embeds pointer of Thing; *Thing is nil", 602 dst: &Model{}, 603 payload: &OnePayload{ 604 Data: &Node{ 605 Type: "models", 606 ID: "1", 607 Attributes: map[string]interface{}{ 608 "bar": "barry", 609 "bat": "batty", 610 "foo": "fooey", 611 }, 612 }, 613 }, 614 expected: &Model{ 615 ModelID: 1, 616 Foo: "fooey", 617 Bar: "barry", 618 Bat: "batty", 619 }, 620 }) 621 } 622 for _, scenario := range scenarios { 623 t.Logf("running scenario: %s\n", scenario.name) 624 625 // get the expected model and marshal to jsonapi 626 buf := bytes.NewBuffer(nil) 627 if err := MarshalPayload(buf, scenario.expected); err != nil { 628 t.Fatal(err) 629 } 630 631 // get the node model representation and marshal to jsonapi 632 payload, err := json.Marshal(scenario.payload) 633 if err != nil { 634 t.Fatal(err) 635 } 636 637 // assert that we're starting w/ the same payload 638 isJSONEqual, err := isJSONEqual(payload, buf.Bytes()) 639 if err != nil { 640 t.Fatal(err) 641 } 642 if !isJSONEqual { 643 t.Errorf("Got\n%s\nExpected\n%s\n", buf.Bytes(), payload) 644 } 645 646 // run jsonapi unmarshal 647 if err := UnmarshalPayload(bytes.NewReader(payload), scenario.dst); err != nil { 648 t.Fatal(err) 649 } 650 651 // assert decoded and expected models are equal 652 if !reflect.DeepEqual(scenario.expected, scenario.dst) { 653 t.Errorf("Got\n%#v\nExpected\n%#v\n", scenario.dst, scenario.expected) 654 } 655 } 656 } 657 658 func TestMarshal_duplicatePrimaryAnnotationFromEmbeddedStructs(t *testing.T) { 659 type Outer struct { 660 ID string `jsonapi:"primary,outer"` 661 Comment 662 *Post 663 } 664 665 o := Outer{ 666 ID: "outer", 667 Comment: Comment{ID: 1}, 668 Post: &Post{ID: 5}, 669 } 670 var payloadData map[string]interface{} 671 672 // Test the standard libraries JSON handling of dup (ID) fields - it uses 673 // the Outer's ID 674 jsonData, err := json.Marshal(o) 675 if err != nil { 676 t.Fatal(err) 677 } 678 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 679 t.Fatal(err) 680 } 681 if e, a := o.ID, payloadData["ID"]; e != a { 682 t.Fatalf("Was expecting ID to be %v, got %v", e, a) 683 } 684 685 // Test the JSONAPI lib handling of dup (ID) fields 686 jsonAPIData := new(bytes.Buffer) 687 if err := MarshalPayload(jsonAPIData, &o); err != nil { 688 t.Fatal(err) 689 } 690 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 691 t.Fatal(err) 692 } 693 data := payloadData["data"].(map[string]interface{}) 694 id := data["id"].(string) 695 if e, a := o.ID, id; e != a { 696 t.Fatalf("Was expecting ID to be %v, got %v", e, a) 697 } 698 } 699 700 func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructs(t *testing.T) { 701 type Foo struct { 702 Count uint `json:"count" jsonapi:"attr,count"` 703 } 704 type Bar struct { 705 Count uint `json:"count" jsonapi:"attr,count"` 706 } 707 type Outer struct { 708 ID uint `json:"id" jsonapi:"primary,outer"` 709 Foo 710 Bar 711 } 712 o := Outer{ 713 ID: 1, 714 Foo: Foo{Count: 1}, 715 Bar: Bar{Count: 2}, 716 } 717 718 var payloadData map[string]interface{} 719 720 // The standard JSON lib will not serialize either embedded struct's fields if 721 // a duplicate is encountered 722 jsonData, err := json.Marshal(o) 723 if err != nil { 724 t.Fatal(err) 725 } 726 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 727 t.Fatal(err) 728 } 729 if _, found := payloadData["count"]; found { 730 t.Fatalf("Was not expecting to find the `count` key in the JSON") 731 } 732 733 // Test the JSONAPI lib handling of dup (attr) fields 734 jsonAPIData := new(bytes.Buffer) 735 if err := MarshalPayload(jsonAPIData, &o); err != nil { 736 t.Fatal(err) 737 } 738 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 739 t.Fatal(err) 740 } 741 data := payloadData["data"].(map[string]interface{}) 742 if _, found := data["attributes"]; found { 743 t.Fatal("Was not expecting to find any `attributes` in the JSON API") 744 } 745 } 746 747 func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsPtrs(t *testing.T) { 748 type Foo struct { 749 Count uint `json:"count" jsonapi:"attr,count"` 750 } 751 type Bar struct { 752 Count uint `json:"count" jsonapi:"attr,count"` 753 } 754 type Outer struct { 755 ID uint `json:"id" jsonapi:"primary,outer"` 756 *Foo 757 *Bar 758 } 759 o := Outer{ 760 ID: 1, 761 Foo: &Foo{Count: 1}, 762 Bar: &Bar{Count: 2}, 763 } 764 765 var payloadData map[string]interface{} 766 767 // The standard JSON lib will not serialize either embedded struct's fields if 768 // a duplicate is encountered 769 jsonData, err := json.Marshal(o) 770 if err != nil { 771 t.Fatal(err) 772 } 773 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 774 t.Fatal(err) 775 } 776 if _, found := payloadData["count"]; found { 777 t.Fatalf("Was not expecting to find the `count` key in the JSON") 778 } 779 780 // Test the JSONAPI lib handling of dup (attr) fields 781 jsonAPIData := new(bytes.Buffer) 782 if err := MarshalPayload(jsonAPIData, &o); err != nil { 783 t.Fatal(err) 784 } 785 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 786 t.Fatal(err) 787 } 788 data := payloadData["data"].(map[string]interface{}) 789 if _, found := data["attributes"]; found { 790 t.Fatal("Was not expecting to find any `attributes` in the JSON API") 791 } 792 } 793 794 func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsMixed(t *testing.T) { 795 type Foo struct { 796 Count uint `json:"count" jsonapi:"attr,count"` 797 } 798 type Bar struct { 799 Count uint `json:"count" jsonapi:"attr,count"` 800 } 801 type Outer struct { 802 ID uint `json:"id" jsonapi:"primary,outer"` 803 *Foo 804 Bar 805 } 806 o := Outer{ 807 ID: 1, 808 Foo: &Foo{Count: 1}, 809 Bar: Bar{Count: 2}, 810 } 811 812 var payloadData map[string]interface{} 813 814 // The standard JSON lib will not serialize either embedded struct's fields if 815 // a duplicate is encountered 816 jsonData, err := json.Marshal(o) 817 if err != nil { 818 t.Fatal(err) 819 } 820 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 821 t.Fatal(err) 822 } 823 if _, found := payloadData["count"]; found { 824 t.Fatalf("Was not expecting to find the `count` key in the JSON") 825 } 826 827 // Test the JSONAPI lib handling of dup (attr) fields; it should serialize 828 // neither 829 jsonAPIData := new(bytes.Buffer) 830 if err := MarshalPayload(jsonAPIData, &o); err != nil { 831 t.Fatal(err) 832 } 833 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 834 t.Fatal(err) 835 } 836 data := payloadData["data"].(map[string]interface{}) 837 if _, found := data["attributes"]; found { 838 t.Fatal("Was not expecting to find any `attributes` in the JSON API") 839 } 840 } 841 842 func TestMarshal_duplicateFieldFromEmbeddedStructs_serializationNameDiffers(t *testing.T) { 843 type Foo struct { 844 Count uint `json:"foo-count" jsonapi:"attr,foo-count"` 845 } 846 type Bar struct { 847 Count uint `json:"bar-count" jsonapi:"attr,bar-count"` 848 } 849 type Outer struct { 850 ID uint `json:"id" jsonapi:"primary,outer"` 851 Foo 852 Bar 853 } 854 o := Outer{ 855 ID: 1, 856 Foo: Foo{Count: 1}, 857 Bar: Bar{Count: 2}, 858 } 859 860 var payloadData map[string]interface{} 861 862 // The standard JSON lib will both the fields since their annotation name 863 // differs 864 jsonData, err := json.Marshal(o) 865 if err != nil { 866 t.Fatal(err) 867 } 868 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 869 t.Fatal(err) 870 } 871 fooJSON, fooFound := payloadData["foo-count"] 872 if !fooFound { 873 t.Fatal("Was expecting to find the `foo-count` key in the JSON") 874 } 875 if e, a := o.Foo.Count, fooJSON.(float64); e != uint(a) { 876 t.Fatalf("Was expecting the `foo-count` value to be %v, got %v", e, a) 877 } 878 barJSON, barFound := payloadData["bar-count"] 879 if !barFound { 880 t.Fatal("Was expecting to find the `bar-count` key in the JSON") 881 } 882 if e, a := o.Bar.Count, barJSON.(float64); e != uint(a) { 883 t.Fatalf("Was expecting the `bar-count` value to be %v, got %v", e, a) 884 } 885 886 // Test the JSONAPI lib handling; it should serialize both 887 jsonAPIData := new(bytes.Buffer) 888 if err := MarshalPayload(jsonAPIData, &o); err != nil { 889 t.Fatal(err) 890 } 891 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 892 t.Fatal(err) 893 } 894 data := payloadData["data"].(map[string]interface{}) 895 attributes := data["attributes"].(map[string]interface{}) 896 fooJSONAPI, fooFound := attributes["foo-count"] 897 if !fooFound { 898 t.Fatal("Was expecting to find the `foo-count` attribute in the JSON API") 899 } 900 if e, a := o.Foo.Count, fooJSONAPI.(float64); e != uint(e) { 901 t.Fatalf("Was expecting the `foo-count` attrobute to be %v, got %v", e, a) 902 } 903 barJSONAPI, barFound := attributes["bar-count"] 904 if !barFound { 905 t.Fatal("Was expecting to find the `bar-count` attribute in the JSON API") 906 } 907 if e, a := o.Bar.Count, barJSONAPI.(float64); e != uint(e) { 908 t.Fatalf("Was expecting the `bar-count` attrobute to be %v, got %v", e, a) 909 } 910 } 911 912 func TestMarshal_embeddedStruct_providesDuplicateAttr(t *testing.T) { 913 type Foo struct { 914 Number uint `json:"count" jsonapi:"attr,count"` 915 } 916 type Outer struct { 917 Foo 918 ID uint `json:"id" jsonapi:"primary,outer"` 919 Count uint `json:"count" jsonapi:"attr,count"` 920 } 921 o := Outer{ 922 ID: 1, 923 Count: 1, 924 Foo: Foo{Number: 5}, 925 } 926 var payloadData map[string]interface{} 927 928 // The standard JSON lib will take the count annotated field from the Outer 929 jsonData, err := json.Marshal(o) 930 if err != nil { 931 t.Fatal(err) 932 } 933 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 934 t.Fatal(err) 935 } 936 if e, a := o.Count, payloadData["count"].(float64); e != uint(a) { 937 t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a) 938 } 939 940 // In JSON API the handling should be that the Outer annotated count field is 941 // serialized into `attributes` 942 jsonAPIData := new(bytes.Buffer) 943 if err := MarshalPayload(jsonAPIData, &o); err != nil { 944 t.Fatal(err) 945 } 946 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 947 t.Fatal(err) 948 } 949 data := payloadData["data"].(map[string]interface{}) 950 attributes := data["attributes"].(map[string]interface{}) 951 if e, a := o.Count, attributes["count"].(float64); e != uint(a) { 952 t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a) 953 } 954 } 955 956 func TestMarshal_embeddedStructPtr_providesDuplicateAttr(t *testing.T) { 957 type Foo struct { 958 Number uint `json:"count" jsonapi:"attr,count"` 959 } 960 type Outer struct { 961 *Foo 962 ID uint `json:"id" jsonapi:"primary,outer"` 963 Count uint `json:"count" jsonapi:"attr,count"` 964 } 965 o := Outer{ 966 ID: 1, 967 Count: 1, 968 Foo: &Foo{Number: 5}, 969 } 970 var payloadData map[string]interface{} 971 972 // The standard JSON lib will take the count annotated field from the Outer 973 jsonData, err := json.Marshal(o) 974 if err != nil { 975 t.Fatal(err) 976 } 977 if err := json.Unmarshal(jsonData, &payloadData); err != nil { 978 t.Fatal(err) 979 } 980 if e, a := o.Count, payloadData["count"].(float64); e != uint(a) { 981 t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a) 982 } 983 984 // In JSON API the handling should be that the Outer annotated count field is 985 // serialized into `attributes` 986 jsonAPIData := new(bytes.Buffer) 987 if err := MarshalPayload(jsonAPIData, &o); err != nil { 988 t.Fatal(err) 989 } 990 if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil { 991 t.Fatal(err) 992 } 993 data := payloadData["data"].(map[string]interface{}) 994 attributes := data["attributes"].(map[string]interface{}) 995 if e, a := o.Count, attributes["count"].(float64); e != uint(a) { 996 t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a) 997 } 998 } 999 1000 // TODO: test permutation of relations with embedded structs