Skip to content

Commit 5335fe6

Browse files
vierbergenlarsodrotbohm
authored andcommitted
Fix mapping URIs to @JsonProperty annotated associations
Normally, when creating and updating (POST/PUT) an entity via the REST endpoints, you can use the URL of the relation target. (e.g.: send `{"package": "/packages/1"}` when `package` is a JPA `@OneToOne` relation). Now this also takes into account when the JPA relation is annotated with `@JsonProperty` to change the serialized name. Add unit tests for linkable associations. Issue: #2165
1 parent 8d9855b commit 5335fe6

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Map.Entry;
27+
import java.util.Objects;
2728
import java.util.Optional;
2829

2930
import org.slf4j.Logger;
@@ -82,6 +83,8 @@
8283
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
8384
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
8485
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
86+
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
87+
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
8588
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
8689
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
8790
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -410,6 +413,7 @@ private Object toModel(Object value, SerializerProvider provider) throws JsonMap
410413
* non-optional associations can be populated on resource creation.
411414
*
412415
* @author Oliver Gierke
416+
* @author Lars Vierbergen
413417
*/
414418
public static class AssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {
415419

@@ -444,7 +448,18 @@ public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanD
444448
while (properties.hasNext()) {
445449

446450
SettableBeanProperty property = properties.next();
447-
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getName());
451+
// To find the PersistentProperty name in case there is a @JsonProperty annotation
452+
// on the field. Both BeanPropertyDefinition#getName() and BeanPropertyDefinition#getInternalName()
453+
// don't return the actual name of the field, so we look up the AnnotatedField itself to retrieve
454+
// the real name from, so it can be used for PersistentProperty lookup
455+
String persistentPropertyName = beanDesc.findProperties().stream()
456+
.filter(propertyDefinition -> property.getName().equals(propertyDefinition.getName()))
457+
.map(BeanPropertyDefinition::getField).filter(Objects::nonNull).map(AnnotatedField::getName).findFirst()
458+
// Fall back to the JSON name in case we can't find a BeanPropertyDefinition,
459+
// so things can be mapped by convention in case they are immutable objects and are
460+
// using constructor injection
461+
.orElse(property.getName());
462+
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(persistentPropertyName);
448463

449464
if (persistentProperty == null) {
450465
continue;

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.mockito.ArgumentMatchers.*;
2020
import static org.mockito.Mockito.*;
2121

22+
import lombok.AccessLevel;
2223
import lombok.Data;
2324
import lombok.Getter;
2425

@@ -96,6 +97,7 @@ void setUp() {
9697

9798
KeyValueMappingContext<?, ?> mappingContext = new KeyValueMappingContext<>();
9899
mappingContext.getPersistentEntity(Sample.class);
100+
mappingContext.getPersistentEntity(Package.class);
99101
mappingContext.getPersistentEntity(SampleWithAdditionalGetters.class);
100102
mappingContext.getPersistentEntity(PersistentEntityJackson2ModuleUnitTests.PetOwner.class);
101103
mappingContext.getPersistentEntity(Immutable.class);
@@ -157,6 +159,41 @@ void resolvesReferenceToSubtypeCorrectly() throws IOException {
157159
assertThat(petOwner.getPet()).isNotNull();
158160
}
159161

162+
@Test
163+
void allowsUrlsForLinkableAssociation() throws Exception {
164+
165+
when(converter.convert(UriTemplate.of("/homes/1").expand(), TypeDescriptor.valueOf(URI.class),
166+
TypeDescriptor.valueOf(Home.class))).thenReturn(new Home());
167+
168+
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
169+
.getRequiredPersistentProperty("home");
170+
171+
when(associations.isLinkableAssociation(property)).thenReturn(true);
172+
173+
PetOwner petOwner = mapper.readValue("{\"home\": \"/homes/1\" }", PetOwner.class);
174+
175+
assertThat(petOwner).isNotNull();
176+
assertThat(petOwner.getHome()).isInstanceOf(Home.class);
177+
}
178+
179+
@Test
180+
void allowsUrlsForRenamedLinkableAssociation() throws IOException {
181+
182+
when(converter.convert(UriTemplate.of("/packages/1").expand(), TypeDescriptor.valueOf(URI.class),
183+
TypeDescriptor.valueOf(Package.class))).thenReturn(new Package());
184+
185+
PersistentProperty<?> property = persistentEntities.getRequiredPersistentEntity(PetOwner.class)
186+
.getRequiredPersistentProperty("_package");
187+
188+
when(associations.isLinkableAssociation(property)).thenReturn(true);
189+
190+
PetOwner petOwner = mapper.readValue("{\"package\":\"/packages/1\"}", PetOwner.class);
191+
192+
assertThat(petOwner).isNotNull();
193+
assertThat(petOwner.getPackage()).isNotNull();
194+
}
195+
196+
160197
@Test // DATAREST-1321
161198
void allowsNumericIdsForLookupTypes() throws Exception {
162199

@@ -260,8 +297,17 @@ static class PetOwner {
260297

261298
Pet pet;
262299
Home home;
300+
301+
@Getter(value = AccessLevel.NONE)
302+
@JsonProperty("package") Package _package;
303+
304+
public Package getPackage() {
305+
return _package;
306+
}
263307
}
264308

309+
static class Package {}
310+
265311
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS)
266312
static class Pet {}
267313

0 commit comments

Comments
 (0)