Skip to content

Commit 75796b8

Browse files
author
Michael Sparer
committed
Fix DomainObjectReader merges for @JsonAnySetter properties
Closes spring-projects#2407 Related tickets spring-projects#2407
1 parent 21e11ad commit 75796b8

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ <T> T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exception {
257257
}
258258

259259
PersistentProperty<?> property = mappedProperties.getPersistentProperty(fieldName);
260+
261+
// This means there's an @JsonAnySetter present for fieldName
262+
if (property == null) {
263+
continue;
264+
}
265+
260266
Optional<Object> rawValue = Optional.ofNullable(accessor.getProperty(property));
261267

262268
if (!rawValue.isPresent() || associationLinks.isLinkableAssociation(property)) {

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.springframework.data.rest.webmvc.mapping.Associations;
4646
import org.springframework.util.ObjectUtils;
4747

48+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
49+
import com.fasterxml.jackson.annotation.JsonAnySetter;
4850
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4951
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
5052
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -110,6 +112,7 @@ void setUp() {
110112
mappingContext.getPersistentEntity(BugModel.class);
111113
mappingContext.getPersistentEntity(ArrayListHolder.class);
112114
mappingContext.getPersistentEntity(MapWrapper.class);
115+
mappingContext.getPersistentEntity(SlotsContainer.class);
113116
mappingContext.afterPropertiesSet();
114117

115118
this.entities = new PersistentEntities(Collections.singleton(mappingContext));
@@ -804,6 +807,36 @@ void augmentsCollectionForPatch() throws Exception {
804807
assertThat(result.items.get(1).some).isEqualTo("otherValue");
805808
assertThat(result.items.get(2).some).isEqualTo("yetAnotherValue");
806809
}
810+
811+
@Test // GH-2407
812+
void deserializesAnySetterForPatch() throws Exception {
813+
814+
Slots slots = new Slots();
815+
slots.slots.add(new Slot("slot-1", 1));
816+
slots.slots.add(new Slot("slot-2", null));
817+
slots.slots.add(new Slot("slot-3", 3));
818+
slots.slots.add(new Slot("slot-4", 4));
819+
820+
SlotsContainer container = new SlotsContainer();
821+
container.slots = slots;
822+
823+
// changing value of slot-1, setting one for slot-2, removing for slot-4, leaving slot-3 unchanged
824+
JsonNode node = new ObjectMapper()
825+
.readTree("{ \"slots\" : { \"slot-1\" : 12 , \"slot-2\" : 2, \"slot-4\" : null } }");
826+
827+
SlotsContainer result = reader.doMerge((ObjectNode) node, container, new ObjectMapper());
828+
829+
final List<Slot> list = new ArrayList<>(result.slots.slots);
830+
assertThat(list).hasSize(4);
831+
assertThat(list.get(0).name).isEqualTo("slot-1");
832+
assertThat(list.get(0).value).isEqualTo(12);
833+
assertThat(list.get(1).name).isEqualTo("slot-2");
834+
assertThat(list.get(1).value).isEqualTo(2);
835+
assertThat(list.get(2).name).isEqualTo("slot-3");
836+
assertThat(list.get(2).value).isEqualTo(3);
837+
assertThat(list.get(3).name).isEqualTo("slot-4");
838+
assertThat(list.get(3).value).isNull();
839+
}
807840

808841
@SuppressWarnings("unchecked")
809842
private static <T> T as(Object source, Class<T> type) {
@@ -1201,4 +1234,51 @@ public void setValues(Collection<String> values) {
12011234
static class MapWrapper {
12021235
public Map<String, Object> map = new HashMap<>();
12031236
}
1237+
1238+
// GH-2407
1239+
1240+
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
1241+
static class SlotsContainer {
1242+
1243+
Slots slots;
1244+
1245+
}
1246+
1247+
static class Slots {
1248+
1249+
// The internal, non-json representation is a Set
1250+
@JsonIgnore private SortedSet<Slot> slots = new TreeSet<>();
1251+
1252+
@JsonAnySetter
1253+
public void put(final String name, final Integer value) {
1254+
slots.removeIf(slot -> slot.name.equals(name));
1255+
slots.add(new Slot(name, value));
1256+
}
1257+
1258+
// We expose however a Map
1259+
@JsonAnyGetter
1260+
public Map<String, Integer> toMap() {
1261+
final Map<String, Integer> map = new HashMap<>();
1262+
slots.forEach(slot -> map.put(slot.name, slot.value));
1263+
return map;
1264+
}
1265+
}
1266+
1267+
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
1268+
static class Slot implements Comparable<Slot> {
1269+
1270+
String name;
1271+
Integer value;
1272+
1273+
Slot(String name, Integer value) {
1274+
this.name = name;
1275+
this.value = value;
1276+
}
1277+
1278+
@Override
1279+
public int compareTo(Slot o) {
1280+
return name.compareTo(o.name);
1281+
}
1282+
1283+
}
12041284
}

0 commit comments

Comments
 (0)