1package jsonapi 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "reflect" 9 "sort" 10 "strings" 11 "testing" 12 "time" 13) 14 15func TestUnmarshall_attrStringSlice(t *testing.T) { 16 out := &Book{} 17 tags := []string{"fiction", "sale"} 18 data := map[string]interface{}{ 19 "data": map[string]interface{}{ 20 "type": "books", 21 "id": "1", 22 "attributes": map[string]interface{}{"tags": tags}, 23 }, 24 } 25 b, err := json.Marshal(data) 26 if err != nil { 27 t.Fatal(err) 28 } 29 30 if err := UnmarshalPayload(bytes.NewReader(b), out); err != nil { 31 t.Fatal(err) 32 } 33 34 if e, a := len(tags), len(out.Tags); e != a { 35 t.Fatalf("Was expecting %d tags, got %d", e, a) 36 } 37 38 sort.Strings(tags) 39 sort.Strings(out.Tags) 40 41 for i, tag := range tags { 42 if e, a := tag, out.Tags[i]; e != a { 43 t.Fatalf("At index %d, was expecting %s got %s", i, e, a) 44 } 45 } 46} 47 48func TestUnmarshalToStructWithPointerAttr(t *testing.T) { 49 out := new(WithPointer) 50 in := map[string]interface{}{ 51 "name": "The name", 52 "is-active": true, 53 "int-val": 8, 54 "float-val": 1.1, 55 } 56 if err := UnmarshalPayload(sampleWithPointerPayload(in), out); err != nil { 57 t.Fatal(err) 58 } 59 if *out.Name != "The name" { 60 t.Fatalf("Error unmarshalling to string ptr") 61 } 62 if !*out.IsActive { 63 t.Fatalf("Error unmarshalling to bool ptr") 64 } 65 if *out.IntVal != 8 { 66 t.Fatalf("Error unmarshalling to int ptr") 67 } 68 if *out.FloatVal != 1.1 { 69 t.Fatalf("Error unmarshalling to float ptr") 70 } 71} 72 73func TestUnmarshalPayload_ptrsAllNil(t *testing.T) { 74 out := new(WithPointer) 75 if err := UnmarshalPayload( 76 strings.NewReader(`{"data": {}}`), out); err != nil { 77 t.Fatalf("Error unmarshalling to Foo") 78 } 79 80 if out.ID != nil { 81 t.Fatalf("Error unmarshalling; expected ID ptr to be nil") 82 } 83} 84 85func TestUnmarshalPayloadWithPointerID(t *testing.T) { 86 out := new(WithPointer) 87 attrs := map[string]interface{}{} 88 89 if err := UnmarshalPayload(sampleWithPointerPayload(attrs), out); err != nil { 90 t.Fatalf("Error unmarshalling to Foo") 91 } 92 93 // these were present in the payload -- expect val to be not nil 94 if out.ID == nil { 95 t.Fatalf("Error unmarshalling; expected ID ptr to be not nil") 96 } 97 if e, a := uint64(2), *out.ID; e != a { 98 t.Fatalf("Was expecting the ID to have a value of %d, got %d", e, a) 99 } 100} 101 102func TestUnmarshalPayloadWithPointerAttr_AbsentVal(t *testing.T) { 103 out := new(WithPointer) 104 in := map[string]interface{}{ 105 "name": "The name", 106 "is-active": true, 107 } 108 109 if err := UnmarshalPayload(sampleWithPointerPayload(in), out); err != nil { 110 t.Fatalf("Error unmarshalling to Foo") 111 } 112 113 // these were present in the payload -- expect val to be not nil 114 if out.Name == nil || out.IsActive == nil { 115 t.Fatalf("Error unmarshalling; expected ptr to be not nil") 116 } 117 118 // these were absent in the payload -- expect val to be nil 119 if out.IntVal != nil || out.FloatVal != nil { 120 t.Fatalf("Error unmarshalling; expected ptr to be nil") 121 } 122} 123 124func TestUnmarshalToStructWithPointerAttr_BadType_bool(t *testing.T) { 125 out := new(WithPointer) 126 in := map[string]interface{}{ 127 "name": true, // This is the wrong type. 128 } 129 expectedErrorMessage := "jsonapi: Can't unmarshal true (bool) to struct field `Name`, which is a pointer to `string`" 130 131 err := UnmarshalPayload(sampleWithPointerPayload(in), out) 132 133 if err == nil { 134 t.Fatalf("Expected error due to invalid type.") 135 } 136 if err.Error() != expectedErrorMessage { 137 t.Fatalf("Unexpected error message: %s", err.Error()) 138 } 139 if _, ok := err.(ErrUnsupportedPtrType); !ok { 140 t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err)) 141 } 142} 143 144func TestUnmarshalToStructWithPointerAttr_BadType_MapPtr(t *testing.T) { 145 out := new(WithPointer) 146 in := map[string]interface{}{ 147 "name": &map[string]interface{}{"a": 5}, // This is the wrong type. 148 } 149 expectedErrorMessage := "jsonapi: Can't unmarshal map[a:5] (map) to struct field `Name`, which is a pointer to `string`" 150 151 err := UnmarshalPayload(sampleWithPointerPayload(in), out) 152 153 if err == nil { 154 t.Fatalf("Expected error due to invalid type.") 155 } 156 if err.Error() != expectedErrorMessage { 157 t.Fatalf("Unexpected error message: %s", err.Error()) 158 } 159 if _, ok := err.(ErrUnsupportedPtrType); !ok { 160 t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err)) 161 } 162} 163 164func TestUnmarshalToStructWithPointerAttr_BadType_Struct(t *testing.T) { 165 out := new(WithPointer) 166 type FooStruct struct{ A int } 167 in := map[string]interface{}{ 168 "name": FooStruct{A: 5}, // This is the wrong type. 169 } 170 expectedErrorMessage := "jsonapi: Can't unmarshal map[A:5] (map) to struct field `Name`, which is a pointer to `string`" 171 172 err := UnmarshalPayload(sampleWithPointerPayload(in), out) 173 174 if err == nil { 175 t.Fatalf("Expected error due to invalid type.") 176 } 177 if err.Error() != expectedErrorMessage { 178 t.Fatalf("Unexpected error message: %s", err.Error()) 179 } 180 if _, ok := err.(ErrUnsupportedPtrType); !ok { 181 t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err)) 182 } 183} 184 185func TestUnmarshalToStructWithPointerAttr_BadType_IntSlice(t *testing.T) { 186 out := new(WithPointer) 187 type FooStruct struct{ A, B int } 188 in := map[string]interface{}{ 189 "name": []int{4, 5}, // This is the wrong type. 190 } 191 expectedErrorMessage := "jsonapi: Can't unmarshal [4 5] (slice) to struct field `Name`, which is a pointer to `string`" 192 193 err := UnmarshalPayload(sampleWithPointerPayload(in), out) 194 195 if err == nil { 196 t.Fatalf("Expected error due to invalid type.") 197 } 198 if err.Error() != expectedErrorMessage { 199 t.Fatalf("Unexpected error message: %s", err.Error()) 200 } 201 if _, ok := err.(ErrUnsupportedPtrType); !ok { 202 t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err)) 203 } 204} 205 206func TestStringPointerField(t *testing.T) { 207 // Build Book payload 208 description := "Hello World!" 209 data := map[string]interface{}{ 210 "data": map[string]interface{}{ 211 "type": "books", 212 "id": "5", 213 "attributes": map[string]interface{}{ 214 "author": "aren55555", 215 "description": description, 216 "isbn": "", 217 }, 218 }, 219 } 220 payload, err := json.Marshal(data) 221 if err != nil { 222 t.Fatal(err) 223 } 224 225 // Parse JSON API payload 226 book := new(Book) 227 if err := UnmarshalPayload(bytes.NewReader(payload), book); err != nil { 228 t.Fatal(err) 229 } 230 231 if book.Description == nil { 232 t.Fatal("Was not expecting a nil pointer for book.Description") 233 } 234 if expected, actual := description, *book.Description; expected != actual { 235 t.Fatalf("Was expecting descript to be `%s`, got `%s`", expected, actual) 236 } 237} 238 239func TestMalformedTag(t *testing.T) { 240 out := new(BadModel) 241 err := UnmarshalPayload(samplePayload(), out) 242 if err == nil || err != ErrBadJSONAPIStructTag { 243 t.Fatalf("Did not error out with wrong number of arguments in tag") 244 } 245} 246 247func TestUnmarshalInvalidJSON(t *testing.T) { 248 in := strings.NewReader("{}") 249 out := new(Blog) 250 251 err := UnmarshalPayload(in, out) 252 253 if err == nil { 254 t.Fatalf("Did not error out the invalid JSON.") 255 } 256} 257 258func TestUnmarshalInvalidJSON_BadType(t *testing.T) { 259 var badTypeTests = []struct { 260 Field string 261 BadValue interface{} 262 Error error 263 }{ // The `Field` values here correspond to the `ModelBadTypes` jsonapi fields. 264 {Field: "string_field", BadValue: 0, Error: ErrUnknownFieldNumberType}, // Expected string. 265 {Field: "float_field", BadValue: "A string.", Error: ErrInvalidType}, // Expected float64. 266 {Field: "time_field", BadValue: "A string.", Error: ErrInvalidTime}, // Expected int64. 267 {Field: "time_ptr_field", BadValue: "A string.", Error: ErrInvalidTime}, // Expected *time / int64. 268 } 269 for _, test := range badTypeTests { 270 t.Run(fmt.Sprintf("Test_%s", test.Field), func(t *testing.T) { 271 out := new(ModelBadTypes) 272 in := map[string]interface{}{} 273 in[test.Field] = test.BadValue 274 expectedErrorMessage := test.Error.Error() 275 276 err := UnmarshalPayload(samplePayloadWithBadTypes(in), out) 277 278 if err == nil { 279 t.Fatalf("Expected error due to invalid type.") 280 } 281 if err.Error() != expectedErrorMessage { 282 t.Fatalf("Unexpected error message: %s", err.Error()) 283 } 284 }) 285 } 286} 287 288func TestUnmarshalSetsID(t *testing.T) { 289 in := samplePayloadWithID() 290 out := new(Blog) 291 292 if err := UnmarshalPayload(in, out); err != nil { 293 t.Fatal(err) 294 } 295 296 if out.ID != 2 { 297 t.Fatalf("Did not set ID on dst interface") 298 } 299} 300 301func TestUnmarshal_nonNumericID(t *testing.T) { 302 data := samplePayloadWithoutIncluded() 303 data["data"].(map[string]interface{})["id"] = "non-numeric-id" 304 payload, _ := payload(data) 305 in := bytes.NewReader(payload) 306 out := new(Post) 307 308 if err := UnmarshalPayload(in, out); err != ErrBadJSONAPIID { 309 t.Fatalf( 310 "Was expecting a `%s` error, got `%s`", 311 ErrBadJSONAPIID, 312 err, 313 ) 314 } 315} 316 317func TestUnmarshalSetsAttrs(t *testing.T) { 318 out, err := unmarshalSamplePayload() 319 if err != nil { 320 t.Fatal(err) 321 } 322 323 if out.CreatedAt.IsZero() { 324 t.Fatalf("Did not parse time") 325 } 326 327 if out.ViewCount != 1000 { 328 t.Fatalf("View count not properly serialized") 329 } 330} 331 332func TestUnmarshalParsesISO8601(t *testing.T) { 333 payload := &OnePayload{ 334 Data: &Node{ 335 Type: "timestamps", 336 Attributes: map[string]interface{}{ 337 "timestamp": "2016-08-17T08:27:12Z", 338 }, 339 }, 340 } 341 342 in := bytes.NewBuffer(nil) 343 json.NewEncoder(in).Encode(payload) 344 345 out := new(Timestamp) 346 347 if err := UnmarshalPayload(in, out); err != nil { 348 t.Fatal(err) 349 } 350 351 expected := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) 352 353 if !out.Time.Equal(expected) { 354 t.Fatal("Parsing the ISO8601 timestamp failed") 355 } 356} 357 358func TestUnmarshalParsesISO8601TimePointer(t *testing.T) { 359 payload := &OnePayload{ 360 Data: &Node{ 361 Type: "timestamps", 362 Attributes: map[string]interface{}{ 363 "next": "2016-08-17T08:27:12Z", 364 }, 365 }, 366 } 367 368 in := bytes.NewBuffer(nil) 369 json.NewEncoder(in).Encode(payload) 370 371 out := new(Timestamp) 372 373 if err := UnmarshalPayload(in, out); err != nil { 374 t.Fatal(err) 375 } 376 377 expected := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) 378 379 if !out.Next.Equal(expected) { 380 t.Fatal("Parsing the ISO8601 timestamp failed") 381 } 382} 383 384func TestUnmarshalInvalidISO8601(t *testing.T) { 385 payload := &OnePayload{ 386 Data: &Node{ 387 Type: "timestamps", 388 Attributes: map[string]interface{}{ 389 "timestamp": "17 Aug 16 08:027 MST", 390 }, 391 }, 392 } 393 394 in := bytes.NewBuffer(nil) 395 json.NewEncoder(in).Encode(payload) 396 397 out := new(Timestamp) 398 399 if err := UnmarshalPayload(in, out); err != ErrInvalidISO8601 { 400 t.Fatalf("Expected ErrInvalidISO8601, got %v", err) 401 } 402} 403 404func TestUnmarshalRelationshipsWithoutIncluded(t *testing.T) { 405 data, _ := payload(samplePayloadWithoutIncluded()) 406 in := bytes.NewReader(data) 407 out := new(Post) 408 409 if err := UnmarshalPayload(in, out); err != nil { 410 t.Fatal(err) 411 } 412 413 // Verify each comment has at least an ID 414 for _, comment := range out.Comments { 415 if comment.ID == 0 { 416 t.Fatalf("The comment did not have an ID") 417 } 418 } 419} 420 421func TestUnmarshalRelationships(t *testing.T) { 422 out, err := unmarshalSamplePayload() 423 if err != nil { 424 t.Fatal(err) 425 } 426 427 if out.CurrentPost == nil { 428 t.Fatalf("Current post was not materialized") 429 } 430 431 if out.CurrentPost.Title != "Bas" || out.CurrentPost.Body != "Fuubar" { 432 t.Fatalf("Attributes were not set") 433 } 434 435 if len(out.Posts) != 2 { 436 t.Fatalf("There should have been 2 posts") 437 } 438} 439 440func TestUnmarshalNullRelationship(t *testing.T) { 441 sample := map[string]interface{}{ 442 "data": map[string]interface{}{ 443 "type": "posts", 444 "id": "1", 445 "attributes": map[string]interface{}{ 446 "body": "Hello", 447 "title": "World", 448 }, 449 "relationships": map[string]interface{}{ 450 "latest_comment": map[string]interface{}{ 451 "data": nil, // empty to-one relationship 452 }, 453 }, 454 }, 455 } 456 data, err := json.Marshal(sample) 457 if err != nil { 458 t.Fatal(err) 459 } 460 461 in := bytes.NewReader(data) 462 out := new(Post) 463 464 if err := UnmarshalPayload(in, out); err != nil { 465 t.Fatal(err) 466 } 467 468 if out.LatestComment != nil { 469 t.Fatalf("Latest Comment was not set to nil") 470 } 471} 472 473func TestUnmarshalNullRelationshipInSlice(t *testing.T) { 474 sample := map[string]interface{}{ 475 "data": map[string]interface{}{ 476 "type": "posts", 477 "id": "1", 478 "attributes": map[string]interface{}{ 479 "body": "Hello", 480 "title": "World", 481 }, 482 "relationships": map[string]interface{}{ 483 "comments": map[string]interface{}{ 484 "data": []interface{}{}, // empty to-many relationships 485 }, 486 }, 487 }, 488 } 489 data, err := json.Marshal(sample) 490 if err != nil { 491 t.Fatal(err) 492 } 493 494 in := bytes.NewReader(data) 495 out := new(Post) 496 497 if err := UnmarshalPayload(in, out); err != nil { 498 t.Fatal(err) 499 } 500 501 if len(out.Comments) != 0 { 502 t.Fatalf("Wrong number of comments; Comments should be empty") 503 } 504} 505 506func TestUnmarshalNestedRelationships(t *testing.T) { 507 out, err := unmarshalSamplePayload() 508 if err != nil { 509 t.Fatal(err) 510 } 511 512 if out.CurrentPost == nil { 513 t.Fatalf("Current post was not materialized") 514 } 515 516 if out.CurrentPost.Comments == nil { 517 t.Fatalf("Did not materialize nested records, comments") 518 } 519 520 if len(out.CurrentPost.Comments) != 2 { 521 t.Fatalf("Wrong number of comments") 522 } 523} 524 525func TestUnmarshalRelationshipsSerializedEmbedded(t *testing.T) { 526 out := sampleSerializedEmbeddedTestModel() 527 528 if out.CurrentPost == nil { 529 t.Fatalf("Current post was not materialized") 530 } 531 532 if out.CurrentPost.Title != "Foo" || out.CurrentPost.Body != "Bar" { 533 t.Fatalf("Attributes were not set") 534 } 535 536 if len(out.Posts) != 2 { 537 t.Fatalf("There should have been 2 posts") 538 } 539 540 if out.Posts[0].LatestComment.Body != "foo" { 541 t.Fatalf("The comment body was not set") 542 } 543} 544 545func TestUnmarshalNestedRelationshipsEmbedded(t *testing.T) { 546 out := bytes.NewBuffer(nil) 547 if err := MarshalOnePayloadEmbedded(out, testModel()); err != nil { 548 t.Fatal(err) 549 } 550 551 model := new(Blog) 552 553 if err := UnmarshalPayload(out, model); err != nil { 554 t.Fatal(err) 555 } 556 557 if model.CurrentPost == nil { 558 t.Fatalf("Current post was not materialized") 559 } 560 561 if model.CurrentPost.Comments == nil { 562 t.Fatalf("Did not materialize nested records, comments") 563 } 564 565 if len(model.CurrentPost.Comments) != 2 { 566 t.Fatalf("Wrong number of comments") 567 } 568 569 if model.CurrentPost.Comments[0].Body != "foo" { 570 t.Fatalf("Comment body not set") 571 } 572} 573 574func TestUnmarshalRelationshipsSideloaded(t *testing.T) { 575 payload := samplePayloadWithSideloaded() 576 out := new(Blog) 577 578 if err := UnmarshalPayload(payload, out); err != nil { 579 t.Fatal(err) 580 } 581 582 if out.CurrentPost == nil { 583 t.Fatalf("Current post was not materialized") 584 } 585 586 if out.CurrentPost.Title != "Foo" || out.CurrentPost.Body != "Bar" { 587 t.Fatalf("Attributes were not set") 588 } 589 590 if len(out.Posts) != 2 { 591 t.Fatalf("There should have been 2 posts") 592 } 593} 594 595func TestUnmarshalNestedRelationshipsSideloaded(t *testing.T) { 596 payload := samplePayloadWithSideloaded() 597 out := new(Blog) 598 599 if err := UnmarshalPayload(payload, out); err != nil { 600 t.Fatal(err) 601 } 602 603 if out.CurrentPost == nil { 604 t.Fatalf("Current post was not materialized") 605 } 606 607 if out.CurrentPost.Comments == nil { 608 t.Fatalf("Did not materialize nested records, comments") 609 } 610 611 if len(out.CurrentPost.Comments) != 2 { 612 t.Fatalf("Wrong number of comments") 613 } 614 615 if out.CurrentPost.Comments[0].Body != "foo" { 616 t.Fatalf("Comment body not set") 617 } 618} 619 620func TestUnmarshalNestedRelationshipsEmbedded_withClientIDs(t *testing.T) { 621 model := new(Blog) 622 623 if err := UnmarshalPayload(samplePayload(), model); err != nil { 624 t.Fatal(err) 625 } 626 627 if model.Posts[0].ClientID == "" { 628 t.Fatalf("ClientID not set from request on related record") 629 } 630} 631 632func unmarshalSamplePayload() (*Blog, error) { 633 in := samplePayload() 634 out := new(Blog) 635 636 if err := UnmarshalPayload(in, out); err != nil { 637 return nil, err 638 } 639 640 return out, nil 641} 642 643func TestUnmarshalManyPayload(t *testing.T) { 644 sample := map[string]interface{}{ 645 "data": []interface{}{ 646 map[string]interface{}{ 647 "type": "posts", 648 "id": "1", 649 "attributes": map[string]interface{}{ 650 "body": "First", 651 "title": "Post", 652 }, 653 }, 654 map[string]interface{}{ 655 "type": "posts", 656 "id": "2", 657 "attributes": map[string]interface{}{ 658 "body": "Second", 659 "title": "Post", 660 }, 661 }, 662 }, 663 } 664 665 data, err := json.Marshal(sample) 666 if err != nil { 667 t.Fatal(err) 668 } 669 in := bytes.NewReader(data) 670 671 posts, err := UnmarshalManyPayload(in, reflect.TypeOf(new(Post))) 672 if err != nil { 673 t.Fatal(err) 674 } 675 676 if len(posts) != 2 { 677 t.Fatal("Wrong number of posts") 678 } 679 680 for _, p := range posts { 681 _, ok := p.(*Post) 682 if !ok { 683 t.Fatal("Was expecting a Post") 684 } 685 } 686} 687 688func TestManyPayload_withLinks(t *testing.T) { 689 firstPageURL := "http://somesite.com/movies?page[limit]=50&page[offset]=50" 690 prevPageURL := "http://somesite.com/movies?page[limit]=50&page[offset]=0" 691 nextPageURL := "http://somesite.com/movies?page[limit]=50&page[offset]=100" 692 lastPageURL := "http://somesite.com/movies?page[limit]=50&page[offset]=500" 693 694 sample := map[string]interface{}{ 695 "data": []interface{}{ 696 map[string]interface{}{ 697 "type": "posts", 698 "id": "1", 699 "attributes": map[string]interface{}{ 700 "body": "First", 701 "title": "Post", 702 }, 703 }, 704 map[string]interface{}{ 705 "type": "posts", 706 "id": "2", 707 "attributes": map[string]interface{}{ 708 "body": "Second", 709 "title": "Post", 710 }, 711 }, 712 }, 713 "links": map[string]interface{}{ 714 KeyFirstPage: firstPageURL, 715 KeyPreviousPage: prevPageURL, 716 KeyNextPage: nextPageURL, 717 KeyLastPage: lastPageURL, 718 }, 719 } 720 721 data, err := json.Marshal(sample) 722 if err != nil { 723 t.Fatal(err) 724 } 725 in := bytes.NewReader(data) 726 727 payload := new(ManyPayload) 728 if err = json.NewDecoder(in).Decode(payload); err != nil { 729 t.Fatal(err) 730 } 731 732 if payload.Links == nil { 733 t.Fatal("Was expecting a non nil ptr Link field") 734 } 735 736 links := *payload.Links 737 738 first, ok := links[KeyFirstPage] 739 if !ok { 740 t.Fatal("Was expecting a non nil ptr Link field") 741 } 742 if e, a := firstPageURL, first; e != a { 743 t.Fatalf("Was expecting links.%s to have a value of %s, got %s", KeyFirstPage, e, a) 744 } 745 746 prev, ok := links[KeyPreviousPage] 747 if !ok { 748 t.Fatal("Was expecting a non nil ptr Link field") 749 } 750 if e, a := prevPageURL, prev; e != a { 751 t.Fatalf("Was expecting links.%s to have a value of %s, got %s", KeyPreviousPage, e, a) 752 } 753 754 next, ok := links[KeyNextPage] 755 if !ok { 756 t.Fatal("Was expecting a non nil ptr Link field") 757 } 758 if e, a := nextPageURL, next; e != a { 759 t.Fatalf("Was expecting links.%s to have a value of %s, got %s", KeyNextPage, e, a) 760 } 761 762 last, ok := links[KeyLastPage] 763 if !ok { 764 t.Fatal("Was expecting a non nil ptr Link field") 765 } 766 if e, a := lastPageURL, last; e != a { 767 t.Fatalf("Was expecting links.%s to have a value of %s, got %s", KeyLastPage, e, a) 768 } 769} 770 771func TestUnmarshalCustomTypeAttributes(t *testing.T) { 772 customInt := CustomIntType(5) 773 customFloat := CustomFloatType(1.5) 774 customString := CustomStringType("Test") 775 776 data := map[string]interface{}{ 777 "data": map[string]interface{}{ 778 "type": "customtypes", 779 "id": "1", 780 "attributes": map[string]interface{}{ 781 "int": customInt, 782 "intptr": &customInt, 783 "intptrnull": nil, 784 785 "float": customFloat, 786 "string": customString, 787 }, 788 }, 789 } 790 payload, err := payload(data) 791 if err != nil { 792 t.Fatal(err) 793 } 794 795 // Parse JSON API payload 796 customAttributeTypes := new(CustomAttributeTypes) 797 if err := UnmarshalPayload(bytes.NewReader(payload), customAttributeTypes); err != nil { 798 t.Fatal(err) 799 } 800 801 if expected, actual := customInt, customAttributeTypes.Int; expected != actual { 802 t.Fatalf("Was expecting custom int to be `%d`, got `%d`", expected, actual) 803 } 804 if expected, actual := customInt, *customAttributeTypes.IntPtr; expected != actual { 805 t.Fatalf("Was expecting custom int pointer to be `%d`, got `%d`", expected, actual) 806 } 807 if customAttributeTypes.IntPtrNull != nil { 808 t.Fatalf("Was expecting custom int pointer to be <nil>, got `%d`", customAttributeTypes.IntPtrNull) 809 } 810 811 if expected, actual := customFloat, customAttributeTypes.Float; expected != actual { 812 t.Fatalf("Was expecting custom float to be `%f`, got `%f`", expected, actual) 813 } 814 if expected, actual := customString, customAttributeTypes.String; expected != actual { 815 t.Fatalf("Was expecting custom string to be `%s`, got `%s`", expected, actual) 816 } 817} 818 819func samplePayloadWithoutIncluded() map[string]interface{} { 820 return map[string]interface{}{ 821 "data": map[string]interface{}{ 822 "type": "posts", 823 "id": "1", 824 "attributes": map[string]interface{}{ 825 "body": "Hello", 826 "title": "World", 827 }, 828 "relationships": map[string]interface{}{ 829 "comments": map[string]interface{}{ 830 "data": []interface{}{ 831 map[string]interface{}{ 832 "type": "comments", 833 "id": "123", 834 }, 835 map[string]interface{}{ 836 "type": "comments", 837 "id": "456", 838 }, 839 }, 840 }, 841 "latest_comment": map[string]interface{}{ 842 "data": map[string]interface{}{ 843 "type": "comments", 844 "id": "55555", 845 }, 846 }, 847 }, 848 }, 849 } 850} 851 852func payload(data map[string]interface{}) (result []byte, err error) { 853 result, err = json.Marshal(data) 854 return 855} 856 857func samplePayload() io.Reader { 858 payload := &OnePayload{ 859 Data: &Node{ 860 Type: "blogs", 861 Attributes: map[string]interface{}{ 862 "title": "New blog", 863 "created_at": 1436216820, 864 "view_count": 1000, 865 }, 866 Relationships: map[string]interface{}{ 867 "posts": &RelationshipManyNode{ 868 Data: []*Node{ 869 { 870 Type: "posts", 871 Attributes: map[string]interface{}{ 872 "title": "Foo", 873 "body": "Bar", 874 }, 875 ClientID: "1", 876 }, 877 { 878 Type: "posts", 879 Attributes: map[string]interface{}{ 880 "title": "X", 881 "body": "Y", 882 }, 883 ClientID: "2", 884 }, 885 }, 886 }, 887 "current_post": &RelationshipOneNode{ 888 Data: &Node{ 889 Type: "posts", 890 Attributes: map[string]interface{}{ 891 "title": "Bas", 892 "body": "Fuubar", 893 }, 894 ClientID: "3", 895 Relationships: map[string]interface{}{ 896 "comments": &RelationshipManyNode{ 897 Data: []*Node{ 898 { 899 Type: "comments", 900 Attributes: map[string]interface{}{ 901 "body": "Great post!", 902 }, 903 ClientID: "4", 904 }, 905 { 906 Type: "comments", 907 Attributes: map[string]interface{}{ 908 "body": "Needs some work!", 909 }, 910 ClientID: "5", 911 }, 912 }, 913 }, 914 }, 915 }, 916 }, 917 }, 918 }, 919 } 920 921 out := bytes.NewBuffer(nil) 922 json.NewEncoder(out).Encode(payload) 923 924 return out 925} 926 927func samplePayloadWithID() io.Reader { 928 payload := &OnePayload{ 929 Data: &Node{ 930 ID: "2", 931 Type: "blogs", 932 Attributes: map[string]interface{}{ 933 "title": "New blog", 934 "view_count": 1000, 935 }, 936 }, 937 } 938 939 out := bytes.NewBuffer(nil) 940 json.NewEncoder(out).Encode(payload) 941 942 return out 943} 944 945func samplePayloadWithBadTypes(m map[string]interface{}) io.Reader { 946 payload := &OnePayload{ 947 Data: &Node{ 948 ID: "2", 949 Type: "badtypes", 950 Attributes: m, 951 }, 952 } 953 954 out := bytes.NewBuffer(nil) 955 json.NewEncoder(out).Encode(payload) 956 957 return out 958} 959 960func sampleWithPointerPayload(m map[string]interface{}) io.Reader { 961 payload := &OnePayload{ 962 Data: &Node{ 963 ID: "2", 964 Type: "with-pointers", 965 Attributes: m, 966 }, 967 } 968 969 out := bytes.NewBuffer(nil) 970 json.NewEncoder(out).Encode(payload) 971 972 return out 973} 974 975func testModel() *Blog { 976 return &Blog{ 977 ID: 5, 978 ClientID: "1", 979 Title: "Title 1", 980 CreatedAt: time.Now(), 981 Posts: []*Post{ 982 { 983 ID: 1, 984 Title: "Foo", 985 Body: "Bar", 986 Comments: []*Comment{ 987 { 988 ID: 1, 989 Body: "foo", 990 }, 991 { 992 ID: 2, 993 Body: "bar", 994 }, 995 }, 996 LatestComment: &Comment{ 997 ID: 1, 998 Body: "foo", 999 }, 1000 }, 1001 { 1002 ID: 2, 1003 Title: "Fuubar", 1004 Body: "Bas", 1005 Comments: []*Comment{ 1006 { 1007 ID: 1, 1008 Body: "foo", 1009 }, 1010 { 1011 ID: 3, 1012 Body: "bas", 1013 }, 1014 }, 1015 LatestComment: &Comment{ 1016 ID: 1, 1017 Body: "foo", 1018 }, 1019 }, 1020 }, 1021 CurrentPost: &Post{ 1022 ID: 1, 1023 Title: "Foo", 1024 Body: "Bar", 1025 Comments: []*Comment{ 1026 { 1027 ID: 1, 1028 Body: "foo", 1029 }, 1030 { 1031 ID: 2, 1032 Body: "bar", 1033 }, 1034 }, 1035 LatestComment: &Comment{ 1036 ID: 1, 1037 Body: "foo", 1038 }, 1039 }, 1040 } 1041} 1042 1043func samplePayloadWithSideloaded() io.Reader { 1044 testModel := testModel() 1045 1046 out := bytes.NewBuffer(nil) 1047 MarshalPayload(out, testModel) 1048 1049 return out 1050} 1051 1052func sampleSerializedEmbeddedTestModel() *Blog { 1053 out := bytes.NewBuffer(nil) 1054 MarshalOnePayloadEmbedded(out, testModel()) 1055 1056 blog := new(Blog) 1057 UnmarshalPayload(out, blog) 1058 1059 return blog 1060} 1061 1062func TestUnmarshalNestedStructPtr(t *testing.T) { 1063 type Director struct { 1064 Firstname string `json:"firstname"` 1065 Surname string `json:"surname"` 1066 } 1067 type Movie struct { 1068 ID string `jsonapi:"primary,movies"` 1069 Name string `jsonapi:"attr,name"` 1070 Director *Director `jsonapi:"attr,director"` 1071 } 1072 sample := map[string]interface{}{ 1073 "data": map[string]interface{}{ 1074 "type": "movies", 1075 "id": "123", 1076 "attributes": map[string]interface{}{ 1077 "name": "The Shawshank Redemption", 1078 "director": map[string]interface{}{ 1079 "firstname": "Frank", 1080 "surname": "Darabont", 1081 }, 1082 }, 1083 }, 1084 } 1085 1086 data, err := json.Marshal(sample) 1087 if err != nil { 1088 t.Fatal(err) 1089 } 1090 in := bytes.NewReader(data) 1091 out := new(Movie) 1092 1093 if err := UnmarshalPayload(in, out); err != nil { 1094 t.Fatal(err) 1095 } 1096 1097 if out.Name != "The Shawshank Redemption" { 1098 t.Fatalf("expected out.Name to be `The Shawshank Redemption`, but got `%s`", out.Name) 1099 } 1100 if out.Director.Firstname != "Frank" { 1101 t.Fatalf("expected out.Director.Firstname to be `Frank`, but got `%s`", out.Director.Firstname) 1102 } 1103 if out.Director.Surname != "Darabont" { 1104 t.Fatalf("expected out.Director.Surname to be `Darabont`, but got `%s`", out.Director.Surname) 1105 } 1106} 1107 1108func TestUnmarshalNestedStruct(t *testing.T) { 1109 1110 boss := map[string]interface{}{ 1111 "firstname": "Hubert", 1112 "surname": "Farnsworth", 1113 "age": 176, 1114 "hired-at": "2016-08-17T08:27:12Z", 1115 } 1116 1117 sample := map[string]interface{}{ 1118 "data": map[string]interface{}{ 1119 "type": "companies", 1120 "id": "123", 1121 "attributes": map[string]interface{}{ 1122 "name": "Planet Express", 1123 "boss": boss, 1124 "founded-at": "2016-08-17T08:27:12Z", 1125 "teams": []Team{ 1126 Team{ 1127 Name: "Dev", 1128 Members: []Employee{ 1129 Employee{Firstname: "Sean"}, 1130 Employee{Firstname: "Iz"}, 1131 }, 1132 Leader: &Employee{Firstname: "Iz"}, 1133 }, 1134 Team{ 1135 Name: "DxE", 1136 Members: []Employee{ 1137 Employee{Firstname: "Akshay"}, 1138 Employee{Firstname: "Peri"}, 1139 }, 1140 Leader: &Employee{Firstname: "Peri"}, 1141 }, 1142 }, 1143 }, 1144 }, 1145 } 1146 1147 data, err := json.Marshal(sample) 1148 if err != nil { 1149 t.Fatal(err) 1150 } 1151 in := bytes.NewReader(data) 1152 out := new(Company) 1153 1154 if err := UnmarshalPayload(in, out); err != nil { 1155 t.Fatal(err) 1156 } 1157 1158 if out.Boss.Firstname != "Hubert" { 1159 t.Fatalf("expected `Hubert` at out.Boss.Firstname, but got `%s`", out.Boss.Firstname) 1160 } 1161 1162 if out.Boss.Age != 176 { 1163 t.Fatalf("expected `176` at out.Boss.Age, but got `%d`", out.Boss.Age) 1164 } 1165 1166 if out.Boss.HiredAt.IsZero() { 1167 t.Fatalf("expected out.Boss.HiredAt to be zero, but got `%t`", out.Boss.HiredAt.IsZero()) 1168 } 1169 1170 if len(out.Teams) != 2 { 1171 t.Fatalf("expected len(out.Teams) to be 2, but got `%d`", len(out.Teams)) 1172 } 1173 1174 if out.Teams[0].Name != "Dev" { 1175 t.Fatalf("expected out.Teams[0].Name to be `Dev`, but got `%s`", out.Teams[0].Name) 1176 } 1177 1178 if out.Teams[1].Name != "DxE" { 1179 t.Fatalf("expected out.Teams[1].Name to be `DxE`, but got `%s`", out.Teams[1].Name) 1180 } 1181 1182 if len(out.Teams[0].Members) != 2 { 1183 t.Fatalf("expected len(out.Teams[0].Members) to be 2, but got `%d`", len(out.Teams[0].Members)) 1184 } 1185 1186 if len(out.Teams[1].Members) != 2 { 1187 t.Fatalf("expected len(out.Teams[1].Members) to be 2, but got `%d`", len(out.Teams[1].Members)) 1188 } 1189 1190 if out.Teams[0].Members[0].Firstname != "Sean" { 1191 t.Fatalf("expected out.Teams[0].Members[0].Firstname to be `Sean`, but got `%s`", out.Teams[0].Members[0].Firstname) 1192 } 1193 1194 if out.Teams[0].Members[1].Firstname != "Iz" { 1195 t.Fatalf("expected out.Teams[0].Members[1].Firstname to be `Iz`, but got `%s`", out.Teams[0].Members[1].Firstname) 1196 } 1197 1198 if out.Teams[1].Members[0].Firstname != "Akshay" { 1199 t.Fatalf("expected out.Teams[1].Members[0].Firstname to be `Akshay`, but got `%s`", out.Teams[1].Members[0].Firstname) 1200 } 1201 1202 if out.Teams[1].Members[1].Firstname != "Peri" { 1203 t.Fatalf("expected out.Teams[1].Members[1].Firstname to be `Peri`, but got `%s`", out.Teams[1].Members[1].Firstname) 1204 } 1205 1206 if out.Teams[0].Leader.Firstname != "Iz" { 1207 t.Fatalf("expected out.Teams[0].Leader.Firstname to be `Iz`, but got `%s`", out.Teams[0].Leader.Firstname) 1208 } 1209 1210 if out.Teams[1].Leader.Firstname != "Peri" { 1211 t.Fatalf("expected out.Teams[1].Leader.Firstname to be `Peri`, but got `%s`", out.Teams[1].Leader.Firstname) 1212 } 1213} 1214 1215func TestUnmarshalNestedStructSlice(t *testing.T) { 1216 1217 fry := map[string]interface{}{ 1218 "firstname": "Philip J.", 1219 "surname": "Fry", 1220 "age": 25, 1221 "hired-at": "2016-08-17T08:27:12Z", 1222 } 1223 1224 bender := map[string]interface{}{ 1225 "firstname": "Bender Bending", 1226 "surname": "Rodriguez", 1227 "age": 19, 1228 "hired-at": "2016-08-17T08:27:12Z", 1229 } 1230 1231 deliveryCrew := map[string]interface{}{ 1232 "name": "Delivery Crew", 1233 "members": []interface{}{ 1234 fry, 1235 bender, 1236 }, 1237 } 1238 1239 sample := map[string]interface{}{ 1240 "data": map[string]interface{}{ 1241 "type": "companies", 1242 "id": "123", 1243 "attributes": map[string]interface{}{ 1244 "name": "Planet Express", 1245 "teams": []interface{}{ 1246 deliveryCrew, 1247 }, 1248 }, 1249 }, 1250 } 1251 1252 data, err := json.Marshal(sample) 1253 if err != nil { 1254 t.Fatal(err) 1255 } 1256 in := bytes.NewReader(data) 1257 out := new(Company) 1258 1259 if err := UnmarshalPayload(in, out); err != nil { 1260 t.Fatal(err) 1261 } 1262 1263 if out.Teams[0].Name != "Delivery Crew" { 1264 t.Fatalf("Nested struct not unmarshalled: Expected `Delivery Crew` but got `%s`", out.Teams[0].Name) 1265 } 1266 1267 if len(out.Teams[0].Members) != 2 { 1268 t.Fatalf("Nested struct not unmarshalled: Expected to have `2` Members but got `%d`", 1269 len(out.Teams[0].Members)) 1270 } 1271 1272 if out.Teams[0].Members[0].Firstname != "Philip J." { 1273 t.Fatalf("Nested struct not unmarshalled: Expected `Philip J.` but got `%s`", 1274 out.Teams[0].Members[0].Firstname) 1275 } 1276} 1277