diff --git a/src/main/java/org/springframework/hateoas/EntityModel.java b/src/main/java/org/springframework/hateoas/EntityModel.java index 20179585..209cc789 100644 --- a/src/main/java/org/springframework/hateoas/EntityModel.java +++ b/src/main/java/org/springframework/hateoas/EntityModel.java @@ -17,10 +17,13 @@ package org.springframework.hateoas; import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonUnwrapped; /** @@ -31,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; */ public class EntityModel extends RepresentationModel> { - private final T content; + private T content; /** * Creates an empty {@link EntityModel}. @@ -75,6 +78,25 @@ public class EntityModel extends RepresentationModel> { return content; } + // Hacks to allow deserialization into an EntityModel> + + @JsonAnySetter + private void setPropertiesAsMap(String key, Object value) { + getOrInitAsMap().put(key, value); + } + + @SuppressWarnings("unchecked") + private Map getOrInitAsMap() { + + if (this.content == null) { + this.content = (T) new LinkedHashMap<>(); + } else { + Assert.state(Map.class.isInstance(this.content), "Content is not a Map!"); + } + + return (Map) this.content; + } + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceSupport#toString() diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java index 401f962f..1c1dd150 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java @@ -46,10 +46,13 @@ import org.springframework.hateoas.server.core.EmbeddedWrappers; import org.springframework.hateoas.server.core.Relation; import org.springframework.lang.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.TypeFactory; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; @@ -569,9 +572,36 @@ class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingIntegrationT assertThat(context.read("$.anotherKey", String.class)).isEqualTo("anotherValue"); } + @Test // #1157 + void deserializesEntityModelForMapCorrectly() throws Exception { + + TypeFactory typeFactory = mapper.getTypeFactory(); + + JavaType mapType = typeFactory.constructParametricType(Map.class, String.class, Object.class); + JavaType modelType = typeFactory.constructParametricType(EntityModel.class, mapType); + + String source = "{ \"key\" : \"value\" }"; + + EntityModel> result = mapper.readValue(source, modelType); + + assertThat(result.getContent()).containsEntry("key", "value"); + } + + @Test // #1157 + void doesNotSetSuperflousPropertiesAsMapIfSpecialContentTypeIsRequested() throws Exception { + + JavaType modelType = mapper.getTypeFactory().constructParametricType(EntityModel.class, SomeSample.class); + + String source = "{ \"name\" : \"Dave\", \"instrument\" : \"Guitar\" }"; + + EntityModel result = mapper.readValue(source, modelType); + + assertThat(result.getContent().name).isEqualTo("Dave"); + } + @Relation(collectionRelation = "someSample") static class SomeSample { - String name; + @JsonProperty String name; } private void verifyResolvedTitle(String resourceBundleKey) throws Exception {