diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java index d676264f3..6c4c5ccf8 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java @@ -763,7 +763,10 @@ public class PersistentEntityJackson2Module extends SimpleModule { */ @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { - return invoker.invokeFindById(p.getValueAsString()); + + Object id = p.getCurrentToken().isNumeric() ? p.getValueAsLong() : p.getValueAsString(); + + return invoker.invokeFindById(id).orElse(null); } } @@ -785,23 +788,22 @@ public class PersistentEntityJackson2Module extends SimpleModule { gen.writeStartArray(); for (Object element : (Collection) value) { - gen.writeString(getLookupKey(element)); + gen.writeObject(getLookupKey(element)); } gen.writeEndArray(); } else { - gen.writeString(getLookupKey(value)); + gen.writeObject(getLookupKey(value)); } } - private String getLookupKey(Object value) { + private Object getLookupKey(Object value) { - Optional> map = lookups.getPluginFor(value.getClass()).map(CastUtils::cast); - - return map + return lookups.getPluginFor(value.getClass()) // + .> map(CastUtils::cast) .orElseThrow(() -> new IllegalArgumentException("No EntityLookup found for " + value.getClass().getName())) - .getResourceIdentifier(value).toString(); + .getResourceIdentifier(value); } } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java index 6bed9e66d..e448e6d38 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java @@ -16,12 +16,16 @@ package org.springframework.data.rest.webmvc.json; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import lombok.Getter; + import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.Collections; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -32,9 +36,11 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.repository.support.RepositoryInvoker; import org.springframework.data.repository.support.RepositoryInvokerFactory; import org.springframework.data.rest.core.UriToEntityConverter; import org.springframework.data.rest.core.mapping.ResourceMappings; +import org.springframework.data.rest.core.support.EntityLookup; import org.springframework.data.rest.core.support.SelfLinkProvider; import org.springframework.data.rest.core.util.Java8PluginRegistry; import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler; @@ -49,6 +55,8 @@ import org.springframework.hateoas.ResourceProcessor; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mvc.ResourceProcessorInvoker; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; @@ -69,6 +77,7 @@ public class PersistentEntityJackson2ModuleUnitTests { @Mock EntityLinks entityLinks; @Mock ResourceMappings mappings; @Mock SelfLinkProvider selfLinks; + @Mock RepositoryInvokerFactory factory; PersistentEntities persistentEntities; ObjectMapper mapper; @@ -90,9 +99,9 @@ public class PersistentEntityJackson2ModuleUnitTests { SimpleModule module = new SimpleModule(); module.setSerializerModifier(new AssociationOmittingSerializerModifier(persistentEntities, associations, - nestedEntitySerializer, new LookupObjectSerializer(Java8PluginRegistry.empty()))); - module.setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(persistentEntities, associations, - converter, mock(RepositoryInvokerFactory.class))); + nestedEntitySerializer, new LookupObjectSerializer(Java8PluginRegistry.of(Arrays.asList(new HomeLookup()))))); + module.setDeserializerModifier( + new AssociationUriResolvingDeserializerModifier(persistentEntities, associations, converter, factory)); this.mapper = new ObjectMapper(); this.mapper.registerModule(module); @@ -135,13 +144,71 @@ public class PersistentEntityJackson2ModuleUnitTests { assertThat(petOwner.getPet()).isNotNull(); } + @Test // DATAREST-1321 + public void allowsNumericIdsForLookupTypes() throws Exception { + + RepositoryInvoker invoker = mock(RepositoryInvoker.class); + when(invoker.invokeFindById(any(Long.class))).thenReturn(Optional.of(new Home())); + + when(factory.getInvokerFor(Home.class)).thenReturn(invoker); + + PersistentProperty property = persistentEntities.getRequiredPersistentEntity(PetOwner.class) + .getRequiredPersistentProperty("home"); + + when(associations.isLookupType(property)).thenReturn(true); + + PetOwner petOwner = mapper.readValue("{\"home\": 1 }", PetOwner.class); + + assertThat(petOwner).isNotNull(); + assertThat(petOwner.getHome()).isInstanceOf(Home.class); + } + + @Test // DATAREST-1321 + public void serializesNonStringLookupValues() throws Exception { + + // Given Pet defined as lookup type + PersistentProperty property = persistentEntities.getRequiredPersistentEntity(PetOwner.class) + .getRequiredPersistentProperty("home"); + when(associations.isLookupType(property)).thenReturn(true); + + // When a Pet is rendered + PetOwner owner = new PetOwner(); + owner.home = new Home(); + + String result = mapper.writeValueAsString(owner); + + // The it appears as numeric value + assertThat(JsonPath.parse(result).read("$.home", Integer.class)) // + .isEqualTo(41); + } + + /** + * @author Oliver Gierke + */ + private static class HomeLookup implements EntityLookup { + + @Override + public Object getResourceIdentifier(Home entity) { + return 41; + } + + @Override + public boolean supports(Class delimiter) { + return delimiter.equals(Home.class); + } + + @Override + public Optional lookupEntity(Object id) { + return Optional.of(new Home()); + } + } + + @Getter + @JsonInclude(Include.NON_NULL) static class PetOwner { Pet pet; - - public Pet getPet() { - return pet; - } + Home home; } @JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS) @@ -149,6 +216,8 @@ public class PersistentEntityJackson2ModuleUnitTests { static class Cat extends Pet {} + static class Home {} + static class Sample { public @JsonProperty("foo") String name; }