diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Person.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Person.java index de7553a31..909065dff 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Person.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Person.java @@ -58,6 +58,9 @@ public class Person { @ManyToOne // private Person father; + @ManyToOne // + private Person grandFather; + @Description("Timestamp this person object was created") // private Date created; @@ -125,6 +128,14 @@ public void setFather(Person father) { this.father = father; } + public Person getGrandFather() { + return grandFather; + } + + public void setGrandFather(Person grandFather) { + this.grandFather = grandFather; + } + public Date getCreated() { return created; } diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java index c6f98e340..43fabcd5b 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.MessageSourceAccessor; @@ -68,6 +69,7 @@ import org.springframework.web.util.UriTemplate; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.jayway.jsonpath.JsonPath; /** @@ -85,7 +87,8 @@ public class PersistentEntitySerializationTests { private static final String PERSON_JSON_IN = "{\"firstName\": \"John\",\"lastName\": \"Doe\"}"; - @Autowired ObjectMapper mapper; + @Autowired @Qualifier("objectMapper") ObjectMapper mapper; + @Autowired @Qualifier("snakeCaseObjectMapper") ObjectMapper snakeCaseMapper; @Autowired Repositories repositories; @Autowired PersonRepository people; @Autowired OrderRepository orders; @@ -94,7 +97,7 @@ public class PersistentEntitySerializationTests { @Configuration static class TestConfig extends RepositoryTestsConfig { - @Bean + @Bean("objectMapper") @Override public ObjectMapper objectMapper() { @@ -103,6 +106,16 @@ public ObjectMapper objectMapper() { new JacksonSerializers(new EnumTranslator(new MessageSourceAccessor(new StaticMessageSource())))); return objectMapper; } + + @Bean("snakeCaseObjectMapper") + public ObjectMapper snakeCaseObjectMapper() { + + ObjectMapper objectMapper = super.objectMapper(); + objectMapper.registerModule( + new JacksonSerializers(new EnumTranslator(new MessageSourceAccessor(new StaticMessageSource())))); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + return objectMapper; + } } LinkDiscoverer linkDiscoverer; @@ -196,6 +209,17 @@ public void deserializesEmbeddedAssociationsCorrectly() throws Exception { assertThat(order.getLineItems()).hasSize(2); } + @Test // DATAREST-1232 + public void deserializesPersonWithSnakeCaseLinkToOtherPersonCorrectly() throws Exception { + + Person grandFather = people.save(new Person("John", "Doe")); + + String child = String.format("{ \"first_name\" : \"Bilbo\", \"grand_father\" : \"/persons/%s\"}", grandFather.getId()); + Person result = snakeCaseMapper.readValue(child, Person.class); + + assertThat(result.getGrandFather()).isEqualTo(grandFather); + } + @Test // DATAREST-250 public void serializesReferencesWithinPagedResourceCorrectly() throws Exception { 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 b8e6ae389..3f41e092f 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 @@ -71,6 +71,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder; @@ -414,6 +415,8 @@ public static class AssociationUriResolvingDeserializerModifier extends BeanDese public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { + boolean isSnakeCase = isSnakeCaseObjectMapper(config); + Iterator properties = builder.getProperties(); entities.getPersistentEntity(beanDesc.getBeanClass()).ifPresent(entity -> { @@ -422,7 +425,7 @@ public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanD SettableBeanProperty property = properties.next(); - PersistentProperty persistentProperty = entity.getPersistentProperty(property.getName()); + PersistentProperty persistentProperty = entity.getPersistentProperty(isSnakeCase ? toCamelCase(property.getName()) : property.getName()); if (persistentProperty == null) { continue; @@ -465,6 +468,41 @@ private static JsonDeserializer wrapIfCollection(PersistentProperty proper CollectionValueInstantiator instantiator = new CollectionValueInstantiator(property); return new CollectionDeserializer(collectionType, elementDeserializer, null, instantiator); } + + private static boolean isSnakeCaseObjectMapper(DeserializationConfig config) { + + if (config.getPropertyNamingStrategy() == null) { + return false; + } + + return config.getPropertyNamingStrategy().getClass().equals(PropertyNamingStrategy.SnakeCaseStrategy.class); + } + + private static String toCamelCase(String value) { + + if (!StringUtils.hasText(value)) { + return value; + } + + if (!value.contains("_")) { + return value; + } + + StringBuilder b = new StringBuilder(); + int length = value.length(); + + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if ((ch == '_') && (i < length - 1)) { + b.append(Character.toUpperCase(value.charAt(i + 1))); + i++; + } else if (ch != '_') { + b.append(ch); + } + } + + return b.toString(); + } } /**