diff --git a/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java b/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java index 484cd3c..7d62a90 100644 --- a/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java +++ b/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java @@ -842,19 +842,36 @@ private ObjectNode getDataNode(Object object, Map includedCo removeField(attributesNode, relationshipField); - if (relationshipObject != null) { + Relationship relationship = configuration.getFieldRelationship(relationshipField); - Relationship relationship = configuration.getFieldRelationship(relationshipField); + // In case serialisation is disabled for a given relationship, skip it + if (!relationship.serialise()) { + continue; + } - // In case serialisation is disabled for a given relationship, skip it - if (!relationship.serialise()) { - continue; - } + // Serialize relationship object + ObjectNode relationshipDataNode = objectMapper.createObjectNode(); + + String relationshipName = relationship.value(); + + // Allow relationships node to have relationship data node even if relationship object is null + relationshipsNode.set(relationshipName, relationshipDataNode); + + // Serialize relationship links + JsonNode relationshipLinks = getRelationshipLinks(object, relationship, selfHref, settings); + + if (relationshipLinks != null) { + // Allow relationship data node to have links even if relationship object is null + relationshipDataNode.set(LINKS, relationshipLinks); + + // Remove link object from serialized JSON + Field refField = configuration + .getRelationshipLinksField(object.getClass(), relationshipName); - String relationshipName = relationship.value(); + removeField(attributesNode, refField); + } - ObjectNode relationshipDataNode = objectMapper.createObjectNode(); - relationshipsNode.set(relationshipName, relationshipDataNode); + if (relationshipObject != null) { // Serialize relationship meta JsonNode relationshipMeta = getRelationshipMeta(object, relationshipName, settings); @@ -868,19 +885,6 @@ private ObjectNode getDataNode(Object object, Map includedCo removeField(attributesNode, refField); } - // Serialize relationship links - JsonNode relationshipLinks = getRelationshipLinks(object, relationship, selfHref, settings); - - if (relationshipLinks != null) { - relationshipDataNode.set(LINKS, relationshipLinks); - - // Remove link object from serialized JSON - Field refField = configuration - .getRelationshipLinksField(object.getClass(), relationshipName); - - removeField(attributesNode, refField); - } - boolean shouldSerializeData = configuration.getFieldRelationship(relationshipField).serialiseData(); if (shouldSerializeData) { if (relationshipObject instanceof Collection) { diff --git a/src/test/java/com/github/jasminb/jsonapi/ResourceConverterTest.java b/src/test/java/com/github/jasminb/jsonapi/ResourceConverterTest.java index 44467c9..f47382c 100644 --- a/src/test/java/com/github/jasminb/jsonapi/ResourceConverterTest.java +++ b/src/test/java/com/github/jasminb/jsonapi/ResourceConverterTest.java @@ -122,12 +122,19 @@ public void testReadWithIncludedSection() throws IOException { } @Test - public void testWriteCollection() throws IOException, IllegalAccessException { + public void testWriteCollection() throws DocumentSerializationException, IOException { InputStream usersRequest = IOUtils.getResource("users.json"); JSONAPIDocument> usersDocument = converter.readDocumentCollection(usersRequest, User.class); List users = usersDocument.get(); - byte[] convertedData = converter.writeObjectCollection(users); + + assertNotNull(users); + assertEquals(2, users.size()); + + // Make sure that relationship object i.e. statuses is null + assertNull(users.get(0).getStatuses()); + + byte[] convertedData = converter.writeDocumentCollection(usersDocument); assertNotNull(convertedData); assertFalse(convertedData.length == 0); @@ -140,14 +147,73 @@ public void testWriteCollection() throws IOException, IllegalAccessException { ObjectMapper mapper = new ObjectMapper(); try { JsonNode node1 = mapper.readTree(IOUtils.getResource("users.json")); + + // Make sure relationship node always get serialized even if relationship object i.e. statuses is null + JsonNode user1Relationships = node1.get("data").get(0).get("relationships"); + assertNotNull(user1Relationships.get("statuses")); + + JsonNode user2Relationships = node1.get("data").get(1).get("relationships"); + assertNotNull(user2Relationships.get("statuses")); + JsonNode node2 = mapper.readTree(convertedData); - assertEquals(node1, node2); + + // Make sure relationship node must always contains one of either meta, link or data node + user1Relationships = node2.get("data").get(0).get("relationships"); + assertNotNull(user1Relationships.get("statuses")); + assertNotNull(user1Relationships.get("statuses").get("links")); + + user2Relationships = node2.get("data").get(1).get("relationships"); + assertNotNull(user2Relationships.get("statuses")); + assertNotNull(user2Relationships.get("statuses").get("links")); + + assertNotEquals(node1, node2); } catch (IOException e) { throw new RuntimeException("Unable to read json, make sure is correct", e); } } @Test + public void testLinksForNonIncludedEmptyToManyRelationship() throws IOException, IllegalAccessException { + InputStream apiResponse = IOUtils.getResource("articles-with-non-included-empty-to-many-relationship.json"); + + ObjectMapper articlesMapper = new ObjectMapper(); + articlesMapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + + ResourceConverter articlesConverter = new ResourceConverter(articlesMapper, Article.class, Author.class, + Comment.class); + + JSONAPIDocument> articlesDocument = articlesConverter.readDocumentCollection(apiResponse, Article.class); + List
articles = articlesDocument.get(); + + assertNotNull(articles); + assertEquals(1, articles.size()); + + Article article = articles.get(0); + + assertNull(article.getComments()); + + assertNull(article.getCommentRelationshipLinks()); + + byte[] convertedData = converter.writeObjectCollection(articles); + assertNotNull(convertedData); + assertNotEquals(0, convertedData.length); + + JSONAPIDocument> convertedDocument = converter.readDocumentCollection(new ByteArrayInputStream(convertedData), Article.class); + List
convertedArticles = convertedDocument.get(); + assertNotNull(convertedArticles); + + Article convertedArticle = convertedArticles.get(0); + + assertNull(convertedArticle.getComments()); + + // Make sure Relationship links are getting serialized even if relationship object i.e. comments is null + assertNotNull(convertedArticle.getCommentRelationshipLinks()); + assertEquals("https://api.example.com/articles/1/relationships/comments", convertedArticle.getCommentRelationshipLinks().getSelf().toString()); + assertEquals("https://api.example.com/articles/1/comments", convertedArticle.getCommentRelationshipLinks().getRelated().toString()); + + } + + @Test public void testReadWithMetaAndLinksSection() throws IOException { InputStream apiResponse = IOUtils.getResource("user-with-meta.json"); diff --git a/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java b/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java index 33d28a4..8fbed5e 100644 --- a/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java +++ b/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java @@ -187,6 +187,7 @@ public void testSerializeWithoutId() throws DocumentSerializationException { User user = new User(); user.setName("Name"); + converter.disableSerializationOption(SerializationFeature.INCLUDE_LINKS); byte [] data = converter.writeDocument(new JSONAPIDocument<>(user)); Assert.assertTrue(new String(data).contains(user.getName())); diff --git a/src/test/java/com/github/jasminb/jsonapi/models/Article.java b/src/test/java/com/github/jasminb/jsonapi/models/Article.java index c0aa071..13d5030 100644 --- a/src/test/java/com/github/jasminb/jsonapi/models/Article.java +++ b/src/test/java/com/github/jasminb/jsonapi/models/Article.java @@ -13,7 +13,7 @@ import java.util.Collections; import java.util.List; -@Type("articles") +@Type(value = "articles", path = "/articles/{id}") @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class, property = "id") public class Article { @Id @@ -24,9 +24,12 @@ public class Article { @Relationship(value = "author", resolve = true, relType = RelType.RELATED) private Author author; - @Relationship(value = "comments", resolve = true) + @Relationship(value = "comments", resolve = true, path = "relationships/comments", relatedPath = "comments") private List comments; + @RelationshipLinks(value = "comments") + private Links commentRelationshipLinks; + @Relationship(value = "users", serialiseData = false) private List users; @@ -84,4 +87,12 @@ public Links getUserRelationshipLinks() { public void setUserRelationshipLinks(Links userRelationshipLinks) { this.userRelationshipLinks = userRelationshipLinks; } + + public Links getCommentRelationshipLinks() { + return commentRelationshipLinks; + } + + public void setCommentRelationshipLinks(Links commentRelationshipLinks) { + this.commentRelationshipLinks = commentRelationshipLinks; + } } diff --git a/src/test/java/com/github/jasminb/jsonapi/models/User.java b/src/test/java/com/github/jasminb/jsonapi/models/User.java index 8aafb6e..efc9847 100644 --- a/src/test/java/com/github/jasminb/jsonapi/models/User.java +++ b/src/test/java/com/github/jasminb/jsonapi/models/User.java @@ -10,7 +10,7 @@ import java.util.List; -@Type("users") +@Type(value = "users", path = "/users/{id}") @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class, property = "id") public class User { @@ -26,7 +26,7 @@ public String getToken() { public String id; public String name; - @Relationship("statuses") + @Relationship(value = "statuses", path = "/relationship/statuses") private List statuses; @Meta diff --git a/src/test/resources/articles-with-non-included-empty-to-many-relationship.json b/src/test/resources/articles-with-non-included-empty-to-many-relationship.json new file mode 100644 index 0000000..ee1dbe1 --- /dev/null +++ b/src/test/resources/articles-with-non-included-empty-to-many-relationship.json @@ -0,0 +1,39 @@ +{ + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON API paints my bikeshed!" + }, + "links": { + "self": "http://example.com/articles/1" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + }, + "users": { + "links": { + "self": "http://example.com/articles/1/relationships/users", + "related": "http://example.com/articles/1/users" + } + } + } + }], + "included": [{ + "type": "people", + "id": "9", + "attributes": { + "first-name": "Dan", + "last-name": "Gebhardt", + "twitter": "dgeb" + }, + "links": { + "self": "http://example.com/people/9" + } + }] +} diff --git a/src/test/resources/users.json b/src/test/resources/users.json index 48b73bd..bf187c6 100644 --- a/src/test/resources/users.json +++ b/src/test/resources/users.json @@ -5,6 +5,9 @@ "id": "1", "attributes": { "name": "liz" + }, + "relationships": { + "statuses": {} } }, { @@ -12,6 +15,9 @@ "id": "2", "attributes": { "name": "john" + }, + "relationships": { + "statuses": {} } } ]