Skip to content

Commit 8e1af29

Browse files
committed
DATAREST-977 - Fixed reading of complex enums on collection expansion for PATCH.
When merging collections on PATCH we now don't use the first collection item's type for all elements but inspect the values for each existing element found. When it comes to appending elements to the collection, wen now just stick to the declared component type as type hint for reading the provided value.
1 parent 2738982 commit 8e1af29

File tree

2 files changed

+78
-14
lines changed

2 files changed

+78
-14
lines changed

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

+33-13
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,7 @@ private boolean handleArray(JsonNode node, Object source, ObjectMapper mapper, T
340340
return false;
341341
}
342342

343-
Iterator<Object> iterator = collection.iterator();
344-
TypeInformation<?> componentType = iterator.hasNext() ? //
345-
ClassTypeInformation.from(iterator.next().getClass()) : //
346-
collectionType.getComponentType();
347-
348-
return handleArrayNode((ArrayNode) node, collection, mapper, componentType);
343+
return handleArrayNode((ArrayNode) node, collection, mapper, collectionType.getComponentType());
349344
}
350345

351346
/**
@@ -373,16 +368,15 @@ private boolean handleArrayNode(ArrayNode array, Collection<Object> collection,
373368

374369
if (!value.hasNext()) {
375370

376-
Class<?> type = componentType == null ? Object.class : componentType.getType();
377-
collection.add(mapper.treeToValue(jsonNode, type));
371+
collection.add(mapper.treeToValue(jsonNode, getTypeToMap(null, componentType).getType()));
378372

379373
continue;
380374
}
381375

382376
Object next = value.next();
383377

384378
if (ArrayNode.class.isInstance(jsonNode)) {
385-
return handleArray(jsonNode, next, mapper, componentType);
379+
return handleArray(jsonNode, next, mapper, getTypeToMap(value, componentType));
386380
}
387381

388382
if (ObjectNode.class.isInstance(jsonNode)) {
@@ -417,7 +411,7 @@ private void doMergeNestedMap(Map<Object, Object> source, ObjectNode node, Objec
417411

418412
Iterator<Entry<String, JsonNode>> fields = node.fields();
419413
Class<?> keyType = typeOrObject(type.getComponentType());
420-
Class<?> valueType = typeOrObject(type.getMapValueType());
414+
TypeInformation<?> valueType = type.getMapValueType();
421415

422416
while (fields.hasNext()) {
423417

@@ -427,19 +421,19 @@ private void doMergeNestedMap(Map<Object, Object> source, ObjectNode node, Objec
427421

428422
Object mappedKey = mapper.readValue(quote(key), keyType);
429423
Object sourceValue = source.get(mappedKey);
424+
TypeInformation<?> typeToMap = getTypeToMap(sourceValue, valueType);
430425

431426
if (value instanceof ObjectNode && sourceValue != null) {
432427

433428
doMerge((ObjectNode) value, sourceValue, mapper);
434429

435430
} else if (value instanceof ArrayNode && sourceValue != null) {
436431

437-
handleArray(value, sourceValue, mapper, type);
432+
handleArray(value, sourceValue, mapper, getTypeToMap(sourceValue, typeToMap));
438433

439434
} else {
440435

441-
Class<?> typeToRead = sourceValue != null ? sourceValue.getClass() : valueType;
442-
source.put(mappedKey, mapper.treeToValue(value, typeToRead));
436+
source.put(mappedKey, mapper.treeToValue(value, typeToMap.getType()));
443437
}
444438

445439
fields.remove();
@@ -554,4 +548,30 @@ private static String quote(String source) {
554548
private static Class<?> typeOrObject(TypeInformation<?> type) {
555549
return type == null ? Object.class : type.getType();
556550
}
551+
552+
/**
553+
* Returns the type to read for the given value and default type. The type will be defaulted to {@link Object} if
554+
* missing. If the given value's type is different from the given default (i.e. more concrete) the value's type will
555+
* be used.
556+
*
557+
* @param value can be {@literal null}.
558+
* @param type can be {@literal null}.
559+
* @return
560+
*/
561+
private static TypeInformation<?> getTypeToMap(Object value, TypeInformation<?> type) {
562+
563+
if (type == null) {
564+
type = ClassTypeInformation.OBJECT;
565+
}
566+
567+
if (value == null) {
568+
return type;
569+
}
570+
571+
if (Enum.class.isInstance(value)) {
572+
return ClassTypeInformation.from(((Enum<?>) value).getDeclaringClass());
573+
}
574+
575+
return value.getClass().equals(type.getType()) ? type : ClassTypeInformation.from(value.getClass());
576+
}
557577
}

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

+45-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.rest.webmvc.json;
1717

18-
import static org.hamcrest.CoreMatchers.*;
18+
import static org.hamcrest.Matchers.*;
1919
import static org.junit.Assert.*;
2020
import static org.mockito.Mockito.*;
2121

@@ -93,6 +93,7 @@ public void setUp() {
9393
mappingContext.getPersistentEntity(Parent.class);
9494
mappingContext.getPersistentEntity(Product.class);
9595
mappingContext.getPersistentEntity(TransientReadOnlyProperty.class);
96+
mappingContext.getPersistentEntity(CollectionOfEnumWithMethods.class);
9697
mappingContext.afterPropertiesSet();
9798

9899
PersistentEntities entities = new PersistentEntities(Collections.singleton(mappingContext));
@@ -451,6 +452,20 @@ public void handlesTransientPropertyWithoutFieldProperly() throws Exception {
451452
reader.readPut((ObjectNode) node, new TransientReadOnlyProperty(), mapper);
452453
}
453454

455+
@Test // DATAREST-977
456+
public void readsCollectionOfComplexEnum() throws Exception {
457+
458+
CollectionOfEnumWithMethods sample = new CollectionOfEnumWithMethods();
459+
sample.enums.add(SampleEnum.FIRST);
460+
461+
ObjectMapper mapper = new ObjectMapper();
462+
JsonNode node = mapper.readTree("{ \"enums\" : [ \"SECOND\", \"FIRST\" ] }");
463+
464+
CollectionOfEnumWithMethods result = reader.merge((ObjectNode) node, sample, mapper);
465+
466+
assertThat(result.enums, contains(SampleEnum.SECOND, SampleEnum.FIRST));
467+
}
468+
454469
@SuppressWarnings("unchecked")
455470
private static <T> T as(Object source, Class<T> type) {
456471

@@ -585,4 +600,33 @@ public String getName() {
585600

586601
public void setName(String name) {}
587602
}
603+
604+
// DATAREST-977
605+
606+
interface EnumInterface {
607+
String getFoo();
608+
}
609+
610+
static enum SampleEnum implements EnumInterface {
611+
612+
FIRST {
613+
614+
@Override
615+
public String getFoo() {
616+
return "first";
617+
}
618+
619+
},
620+
SECOND {
621+
622+
public String getFoo() {
623+
return "second";
624+
}
625+
};
626+
}
627+
628+
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
629+
static class CollectionOfEnumWithMethods {
630+
List<SampleEnum> enums = new ArrayList<SampleEnum>();
631+
}
588632
}

0 commit comments

Comments
 (0)