diff --git a/jsonapi.go b/jsonapi.go index 28240af..7f811ba 100644 --- a/jsonapi.go +++ b/jsonapi.go @@ -338,8 +338,8 @@ func (d *document) verifyResourceUniqueness() bool { } topLevelSeen[rid] = true - relSeen := make(map[string]bool) for _, rel := range ro.Relationships { + relSeen := make(map[string]bool) for _, relRo := range rel.getResourceObjectSlice() { relRid := relRo.getIdentifier() if relRo.ID != "" && relSeen[relRid] { diff --git a/jsonapi_test.go b/jsonapi_test.go index f9bcd24..f42c5b9 100644 --- a/jsonapi_test.go +++ b/jsonapi_test.go @@ -182,6 +182,7 @@ var ( articleRelatedCompleteBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"},"meta":{"count":10},"links":{"self":"http://example.com/articles/1/relationships/author","related":"http://example.com/articles/1/author"}},"comments":{"data":[{"id":"1","type":"comments"},{"id":"2","type":"comments"}],"links":{"self":"http://example.com/articles/1/relationships/comments","related":"http://example.com/articles/1/comments"}}}}}` articleRelatedCompleteWithIncludeBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"}},"comments":{"data":[{"id":"1","type":"comments"},{"id":"2","type":"comments"}]}}},"included":[{"id":"1","type":"author","attributes":{"name":"A"}},{"id":"1","type":"comments","attributes":{"body":"A"}},{"id":"2","type":"comments","attributes":{"body":"B"}}]}` articleRelatedNonuniqueLinkage = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"}},"comments":{"data":[{"id":"1","type":"comments"},{"id":"1","type":"comments"}]}}}}` + articleRelatedDifferentRefToSameObject = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"}},"contact":{"data":{"id":"1","type":"author"}},"comments":{"data":[{"id":"1","type":"comments"}]}}}}` articleRelatedCommentsNestedWithIncludeBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"comments":{"data":[{"id":"1","type":"comments"}],"links":{"self":"http://example.com/articles/1/relationships/comments","related":"http://example.com/articles/1/comments"}}}},"included":[{"id":"1","type":"comments","attributes":{"body":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"},"links":{"self":"http://example.com/comments/1/relationships/author","related":"http://example.com/comments/1/author"}}}},{"id":"1","type":"author","attributes":{"name":"A"}}]}` articleWithIncludeOnlyBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"}},"included":[{"id":"1","type":"author","attributes":{"name":"A"}}]}` articleRelatedAuthorLinksOnlyBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"links":{"self":"http://example.com/articles/1/relationships/author","related":"http://example.com/articles/1/author"}}}}}` @@ -442,6 +443,14 @@ type ArticleRelated struct { Comments []*Comment `jsonapi:"relationship" json:"comments,omitempty"` } +type ArticleRelatedWithContact struct { + ID string `jsonapi:"primary,articles"` + Title string `jsonapi:"attribute" json:"title"` + Contact *Author `jsonapi:"relationship" json:"contact,omitempty"` + Author *Author `jsonapi:"relationship" json:"author,omitempty"` + Comments []*Comment `jsonapi:"relationship" json:"comments,omitempty"` +} + func (a *ArticleRelated) LinkRelation(relation string) *Link { return &Link{ Self: fmt.Sprintf("http://example.com/articles/%s/relationships/%s", a.ID, relation), diff --git a/unmarshal_test.go b/unmarshal_test.go index ff93845..524365b 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -435,7 +435,25 @@ func TestUnmarshal(t *testing.T) { }, expect: ArticleRelated{}, expectError: ErrNonuniqueResource, - }, { + }, + { + description: "ArticleRelatedWithContact (nonunique linkage)", + given: articleRelatedDifferentRefToSameObject, + do: func(body []byte) (any, error) { + var a ArticleRelatedWithContact + err := Unmarshal(body, &a) + return &a, err + }, + expect: &ArticleRelatedWithContact{ + ID: "1", + Title: "A", + Author: &Author{ID: "1"}, + Comments: []*Comment{{ID: "1"}}, + Contact: &Author{ID: "1"}, + }, + expectError: nil, + }, + { description: "links member only", given: `{"links":null}`, do: func(body []byte) (any, error) {