diff --git a/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java b/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java index 05176d5..652f2ea 100644 --- a/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java +++ b/src/main/java/com/github/jasminb/jsonapi/ResourceConverter.java @@ -812,7 +812,6 @@ private ObjectNode getDataNode(Object object, Map includedCo // Cache the object for recursion breaking purposes resourceCache.cache(resourceId.concat(configuration.getTypeName(object.getClass())), null); } - dataNode.set(ATTRIBUTES, attributesNode); // Handle relationships (remove from base type and add as relationships) List relationshipFields = configuration.getRelationshipFields(object.getClass()); @@ -916,6 +915,20 @@ private ObjectNode getDataNode(Object object, Map includedCo } } + // Note: Attributes can be rendered once all other top + // level object properties have been removed. + // If done earlier, the presence of these properties + // will cause the isEmpty test to fail in the case + // of a configuration disallowing an empty + // 'attributes' tag to be rendered. + if (canSerializeEmptyAttributesTag(settings)) { + dataNode.set(ATTRIBUTES, attributesNode); // default + } else { + if (!attributesNode.isEmpty(null)) { + dataNode.set(ATTRIBUTES, attributesNode); + } + } + if (jsonLinks != null) { dataNode.set(LINKS, jsonLinks); } @@ -1232,6 +1245,13 @@ private boolean shouldSerializeMeta(SerializationSettings settings) { return serializationFeatures.contains(SerializationFeature.INCLUDE_META); } + private boolean canSerializeEmptyAttributesTag(SerializationSettings settings) { + if (settings != null && settings.serializeEmptyAttributesTag() != null) { + return settings.serializeEmptyAttributesTag(); + } + return serializationFeatures.contains(SerializationFeature.ATTRIBUTES_TAG_ONLY_IF_NOT_EMPTY); + } + private JsonNode removeField(ObjectNode node, Field field) { if (field != null) { return node.remove(namingStrategy.nameForField(null, null, field.getName())); diff --git a/src/main/java/com/github/jasminb/jsonapi/SerializationFeature.java b/src/main/java/com/github/jasminb/jsonapi/SerializationFeature.java index 246f69f..5050db5 100644 --- a/src/main/java/com/github/jasminb/jsonapi/SerializationFeature.java +++ b/src/main/java/com/github/jasminb/jsonapi/SerializationFeature.java @@ -24,10 +24,17 @@ public enum SerializationFeature { /** * If enabled, links attribute will be serialized */ - INCLUDE_LINKS(true); + INCLUDE_LINKS(true), + + /** + * If enabled, resources that only have + * type and id will not render the attributes property. + */ + ATTRIBUTES_TAG_ONLY_IF_NOT_EMPTY(false); final boolean enabled; + SerializationFeature(boolean enabled) { this.enabled = enabled; } diff --git a/src/main/java/com/github/jasminb/jsonapi/SerializationSettings.java b/src/main/java/com/github/jasminb/jsonapi/SerializationSettings.java index 01e4096..4c3a19a 100644 --- a/src/main/java/com/github/jasminb/jsonapi/SerializationSettings.java +++ b/src/main/java/com/github/jasminb/jsonapi/SerializationSettings.java @@ -13,6 +13,7 @@ public class SerializationSettings { private List relationshipExludes; private Boolean serializeMeta; private Boolean serializeLinks; + private Boolean serializeEmptyAttributesTag = true; private SerializationSettings() { // Hide CTOR @@ -52,6 +53,10 @@ public Boolean serializeLinks() { return serializeLinks; } + public Boolean serializeEmptyAttributesTag() { + return serializeEmptyAttributesTag; + } + /** * Serialisation settings builder. */ @@ -60,6 +65,7 @@ public static class Builder { private List relationshipExludes = new ArrayList<>(); private Boolean serializeMeta; private Boolean serializeLinks; + private Boolean serializeEmptyAttributesTag; /** * Explicitly enable relationship serialisation. @@ -70,7 +76,7 @@ public Builder includeRelationship(String relationshipName) { relationshipIncludes.add(relationshipName); return this; } - + /** * Explicitly disable relationship serialisation. * @param relationshipName {@link String} relationship name @@ -100,6 +106,11 @@ public Builder serializeLinks(Boolean flag) { serializeLinks = flag; return this; } + + public Builder serializeEmptyAttributesTag(Boolean flag) { + serializeEmptyAttributesTag = flag; + return this; + } /** * Create new SerialisationSettings instance. @@ -111,6 +122,7 @@ public SerializationSettings build() { result.relationshipExludes = new ArrayList<>(relationshipExludes); result.serializeLinks = serializeLinks; result.serializeMeta = serializeMeta; + result.serializeEmptyAttributesTag = serializeEmptyAttributesTag; return result; } } diff --git a/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java b/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java index 33d28a4..19a49b6 100644 --- a/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java +++ b/src/test/java/com/github/jasminb/jsonapi/SerializationTest.java @@ -1,11 +1,15 @@ package com.github.jasminb.jsonapi; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.github.jasminb.jsonapi.exceptions.DocumentSerializationException; import com.github.jasminb.jsonapi.models.Article; import com.github.jasminb.jsonapi.models.Author; +import com.github.jasminb.jsonapi.models.IntegerIdResource; import com.github.jasminb.jsonapi.models.SimpleMeta; +import com.github.jasminb.jsonapi.models.SimpleResource; import com.github.jasminb.jsonapi.models.Status; import com.github.jasminb.jsonapi.models.User; import com.github.jasminb.jsonapi.models.errors.Error; @@ -13,6 +17,7 @@ import org.junit.Before; import org.junit.Test; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -127,6 +132,22 @@ public void testIncludedDataDisabledTroughSettings() throws DocumentSerializatio Assert.assertNull(status.getContent()); } + @Test + public void testEnableNonEmptyAttributesTagThroughSettings() throws Exception { + SimpleResource source = new SimpleResource(); + source.setId(80085); + JSONAPIDocument document = new JSONAPIDocument<>(source); + + SerializationSettings serializationSettings = new SerializationSettings.Builder() + .serializeEmptyAttributesTag(false ) + .build(); + + converter.registerType(SimpleResource.class); + String s = new String( converter.writeDocument(document, serializationSettings) ); + ObjectMapper m = new ObjectMapper(); + Assert.assertNull( m.readTree(s.getBytes()).get("attributes")); + } + /** * Covers use-case where global settings are used to disable relationship attribute inclusion but * behaviour is changed trouh local settings provided when serialization is executed. @@ -136,18 +157,18 @@ public void testIncludedDataDisabledTroughSettings() throws DocumentSerializatio public void testIncludedDataEnabledTroughSettings() throws DocumentSerializationException { converter.disableSerializationOption(SerializationFeature.INCLUDE_RELATIONSHIP_ATTRIBUTES); JSONAPIDocument document = createDocument(createUser()); - + SerializationSettings serializationSettings = new SerializationSettings.Builder() .includeRelationship("statuses") .build(); - + JSONAPIDocument convertedBack = converter.readDocument( converter.writeDocument(document, serializationSettings), User.class); - + Status status = convertedBack.get().getStatuses().iterator().next(); Assert.assertNotNull(status.getContent()); } - + @Test public void testOverrideGlobalMetaLinksSettings() throws DocumentSerializationException { JSONAPIDocument document = createDocument(createUser()); diff --git a/src/test/java/com/github/jasminb/jsonapi/models/SimpleResource.java b/src/test/java/com/github/jasminb/jsonapi/models/SimpleResource.java new file mode 100644 index 0000000..56c8823 --- /dev/null +++ b/src/test/java/com/github/jasminb/jsonapi/models/SimpleResource.java @@ -0,0 +1,26 @@ +package com.github.jasminb.jsonapi.models; + +import com.github.jasminb.jsonapi.IntegerIdHandler; +import com.github.jasminb.jsonapi.annotations.Id; +import com.github.jasminb.jsonapi.annotations.Type; + +/** + * Model class used to test {@link Integer} as resource identifier. + * + * @author jbegic + */ +@Type("simple-resource-type") +public class SimpleResource { + + @Id(IntegerIdHandler.class) + private Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + +}