1616package org .springframework .data .rest .webmvc .json ;
1717
1818import java .io .InputStream ;
19+ import java .util .Arrays ;
1920import java .util .HashMap ;
21+ import java .util .Collection ;
22+ import java .util .Collections ;
2023import java .util .Iterator ;
2124import java .util .Map ;
2225import java .util .Map .Entry ;
3538import com .fasterxml .jackson .databind .JsonNode ;
3639import com .fasterxml .jackson .databind .ObjectMapper ;
3740import com .fasterxml .jackson .databind .introspect .BasicClassIntrospector ;
41+ import com .fasterxml .jackson .databind .node .ArrayNode ;
3842import com .fasterxml .jackson .databind .introspect .BeanPropertyDefinition ;
3943import com .fasterxml .jackson .databind .introspect .ClassIntrospector ;
4044import com .fasterxml .jackson .databind .node .ObjectNode ;
@@ -155,6 +159,7 @@ public <T> T merge(ObjectNode source, T target, ObjectMapper mapper) {
155159 * @return
156160 * @throws Exception
157161 */
162+ @ SuppressWarnings ("unchecked" )
158163 private <T > T doMerge (ObjectNode root , T target , ObjectMapper mapper ) throws Exception {
159164
160165 Assert .notNull (root , "Root ObjectNode must not be null!" );
@@ -173,29 +178,34 @@ private <T> T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exc
173178
174179 Entry <String , JsonNode > entry = i .next ();
175180 JsonNode child = entry .getValue ();
181+ String fieldName = entry .getKey ();
176182
177- if (child .isArray ()) {
183+ if (!mappedProperties .hasPersistentPropertyForField (fieldName )) {
184+ i .remove ();
178185 continue ;
179186 }
180187
181- String fieldName = entry .getKey ();
188+ PersistentProperty <?> property = mappedProperties .getPersistentProperty (fieldName );
189+ PersistentPropertyAccessor accessor = entity .getPropertyAccessor (target );
190+ Object rawValue = accessor .getProperty (property );
191+
192+ if (child .isArray ()) {
193+
194+ boolean nestedObjectFound = handleArrayNode ((ArrayNode ) child , asCollection (rawValue ), mapper );
195+
196+ if (nestedObjectFound ) {
197+ i .remove ();
198+ }
182199
183- if (!mappedProperties .hasPersistentPropertyForField (fieldName )) {
184- i .remove ();
185200 continue ;
186201 }
187202
188203 if (child .isObject ()) {
189204
190- PersistentProperty <?> property = mappedProperties .getPersistentProperty (fieldName );
191-
192205 if (associationLinks .isLinkableAssociation (property )) {
193206 continue ;
194207 }
195208
196- PersistentPropertyAccessor accessor = entity .getPropertyAccessor (target );
197- Object nested = accessor .getProperty (property );
198-
199209 ObjectNode objectNode = (ObjectNode ) child ;
200210
201211 if (property .isMap ()) {
@@ -205,7 +215,7 @@ private <T> T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exc
205215 continue ;
206216 }
207217
208- doMergeNestedMap ((Map <String , Object >) nested , objectNode , mapper );
218+ doMergeNestedMap ((Map <String , Object >) rawValue , objectNode , mapper );
209219
210220 // Remove potentially emptied Map as values have been handled recursively
211221 if (!objectNode .fieldNames ().hasNext ()) {
@@ -215,15 +225,55 @@ private <T> T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exc
215225 continue ;
216226 }
217227
218- if (nested != null && property .isEntity ()) {
219- doMerge (objectNode , nested , mapper );
228+ if (rawValue != null && property .isEntity ()) {
229+ doMerge (objectNode , rawValue , mapper );
220230 }
221231 }
222232 }
223233
224234 return mapper .readerForUpdating (target ).readValue (root );
225235 }
226236
237+ /**
238+ * Applies the diff handling to {@link ArrayNode}s, potentially recursing into nested ones.
239+ *
240+ * @param array the source {@link ArrayNode}m, must not be {@literal null}.
241+ * @param collection the actual collection values, must not be {@literal null}.
242+ * @param mapper the {@link ObjectMapper} to use, must not be {@literal null}.
243+ * @return whether an object merge has been applied to the {@link ArrayNode}.
244+ */
245+ private boolean handleArrayNode (ArrayNode array , Collection <Object > collection , ObjectMapper mapper )
246+ throws Exception {
247+
248+ Assert .notNull (array , "ArrayNode must not be null!" );
249+ Assert .notNull (collection , "Source collection must not be null!" );
250+ Assert .notNull (mapper , "ObjectMapper must not be null!" );
251+
252+ Iterator <Object > value = collection .iterator ();
253+ boolean nestedObjectFound = false ;
254+
255+ for (JsonNode jsonNode : array ) {
256+
257+ if (!value .hasNext ()) {
258+ return nestedObjectFound ;
259+ }
260+
261+ Object next = value .next ();
262+
263+ if (ArrayNode .class .isInstance (jsonNode )) {
264+ return handleArrayNode (array , asCollection (next ), mapper );
265+ }
266+
267+ if (ObjectNode .class .isInstance (jsonNode )) {
268+
269+ nestedObjectFound = true ;
270+ doMerge ((ObjectNode ) jsonNode , next , mapper );
271+ }
272+ }
273+
274+ return nestedObjectFound ;
275+ }
276+
227277 /**
228278 * Merges nested {@link Map} values for the given source {@link Map}, the {@link ObjectNode} and {@link ObjectMapper}.
229279 *
@@ -253,11 +303,38 @@ private void doMergeNestedMap(Map<String, Object> source, ObjectNode node, Objec
253303 }
254304 }
255305
306+ /**
307+ * Returns the given source instance as {@link Collection}.
308+ *
309+ * @param source can be {@literal null}.
310+ * @return
311+ */
312+ @ SuppressWarnings ("unchecked" )
313+ private static Collection <Object > asCollection (Object source ) {
314+
315+ if (source == null ) {
316+ return Collections .emptyList ();
317+ }
318+
319+ if (source instanceof Collection ) {
320+ return (Collection <Object >) source ;
321+ }
322+
323+ if (source .getClass ().isArray ()) {
324+ return Arrays .asList ((Object []) source );
325+ }
326+
327+ return Collections .singleton (source );
328+ }
329+
256330 /**
257331 * Simple value object to capture a mapping of Jackson mapped field names and {@link PersistentProperty} instances.
332+ *
333+ * @param source can be {@literal null}.
258334 *
259335 * @author Oliver Gierke
260336 */
337+ @ SuppressWarnings ("unchecked" )
261338 static class MappedProperties {
262339
263340 private static final ClassIntrospector INTROSPECTOR = new BasicClassIntrospector ();
0 commit comments