Properly deep-merge nested documents on PUT.

This is needed to consider read-only properties within those objects as those would otherwise get overridden.

Fixes: #2174
This commit is contained in:
Oliver Drotbohm
2023-02-21 10:36:38 +01:00
parent cdb4a5e053
commit f18491f722
2 changed files with 32 additions and 1 deletions

View File

@@ -40,6 +40,7 @@ import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.util.InputStreamHttpInputMessage;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.TypeInformation;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
@@ -61,6 +62,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
* @author Craig Andrews
* @author Mathias Düsterhöft
* @author Thomas Mrozinski
* @author Lars Vierbergen
* @since 2.2
*/
public class DomainObjectReader {
@@ -678,7 +680,7 @@ public class DomainObjectReader {
} else if (property.isCollectionLike()) {
result = mergeCollections(property, sourceValue, targetValue, mapper);
} else if (property.isEntity()) {
result = mergeForPut(sourceValue, targetValue, mapper);
result = Optionals.mapIfAllPresent(sourceValue, targetValue, (l, r) -> mergeForPut(l, r, mapper));
} else {
result = sourceValue;
}

View File

@@ -618,6 +618,33 @@ class DomainObjectReaderUnitTests {
});
}
@Test // #2174
void nestedEntitiesWithReadonlyFieldAreKeptForPut() throws Exception {
Inner inner = new Inner();
inner.name = "inner name";
inner.prop = "something";
inner.readOnly = "readonly value";
inner.hidden = "hidden value";
Outer outer = new Outer();
outer.prop = "else";
outer.name = "outer name";
outer.inner = inner;
JsonNode node = new ObjectMapper().readTree("{ \"inner\" : { \"name\" : \"new inner name\" } }");
Outer result = reader.readPut((ObjectNode) node, outer, new ObjectMapper());
assertThat(result).isSameAs(outer);
assertThat(result.prop).isNull();
assertThat(result.inner.prop).isNull();
assertThat(result.inner.name).isEqualTo("new inner name");
assertThat(result.inner.readOnly).isEqualTo("readonly value");
assertThat(result.inner.hidden).isEqualTo("hidden value");
assertThat(result.inner).isSameAs(inner);
}
@SuppressWarnings("unchecked")
private static <T> T as(Object source, Class<T> type) {
@@ -715,6 +742,8 @@ class DomainObjectReaderUnitTests {
String name;
String prop;
@JsonProperty(access = READ_ONLY) String readOnly;
@JsonIgnore String hidden;
}
@JsonAutoDetect(fieldVisibility = Visibility.ANY)