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.
This commit is contained in:
Oliver Gierke
2017-01-24 16:09:53 +01:00
parent 2738982576
commit 8e1af29bf2
2 changed files with 78 additions and 14 deletions

View File

@@ -340,12 +340,7 @@ public class DomainObjectReader {
return false;
}
Iterator<Object> iterator = collection.iterator();
TypeInformation<?> componentType = iterator.hasNext() ? //
ClassTypeInformation.from(iterator.next().getClass()) : //
collectionType.getComponentType();
return handleArrayNode((ArrayNode) node, collection, mapper, componentType);
return handleArrayNode((ArrayNode) node, collection, mapper, collectionType.getComponentType());
}
/**
@@ -373,8 +368,7 @@ public class DomainObjectReader {
if (!value.hasNext()) {
Class<?> type = componentType == null ? Object.class : componentType.getType();
collection.add(mapper.treeToValue(jsonNode, type));
collection.add(mapper.treeToValue(jsonNode, getTypeToMap(null, componentType).getType()));
continue;
}
@@ -382,7 +376,7 @@ public class DomainObjectReader {
Object next = value.next();
if (ArrayNode.class.isInstance(jsonNode)) {
return handleArray(jsonNode, next, mapper, componentType);
return handleArray(jsonNode, next, mapper, getTypeToMap(value, componentType));
}
if (ObjectNode.class.isInstance(jsonNode)) {
@@ -417,7 +411,7 @@ public class DomainObjectReader {
Iterator<Entry<String, JsonNode>> fields = node.fields();
Class<?> keyType = typeOrObject(type.getComponentType());
Class<?> valueType = typeOrObject(type.getMapValueType());
TypeInformation<?> valueType = type.getMapValueType();
while (fields.hasNext()) {
@@ -427,6 +421,7 @@ public class DomainObjectReader {
Object mappedKey = mapper.readValue(quote(key), keyType);
Object sourceValue = source.get(mappedKey);
TypeInformation<?> typeToMap = getTypeToMap(sourceValue, valueType);
if (value instanceof ObjectNode && sourceValue != null) {
@@ -434,12 +429,11 @@ public class DomainObjectReader {
} else if (value instanceof ArrayNode && sourceValue != null) {
handleArray(value, sourceValue, mapper, type);
handleArray(value, sourceValue, mapper, getTypeToMap(sourceValue, typeToMap));
} else {
Class<?> typeToRead = sourceValue != null ? sourceValue.getClass() : valueType;
source.put(mappedKey, mapper.treeToValue(value, typeToRead));
source.put(mappedKey, mapper.treeToValue(value, typeToMap.getType()));
}
fields.remove();
@@ -554,4 +548,30 @@ public class DomainObjectReader {
private static Class<?> typeOrObject(TypeInformation<?> type) {
return type == null ? Object.class : type.getType();
}
/**
* Returns the type to read for the given value and default type. The type will be defaulted to {@link Object} if
* missing. If the given value's type is different from the given default (i.e. more concrete) the value's type will
* be used.
*
* @param value can be {@literal null}.
* @param type can be {@literal null}.
* @return
*/
private static TypeInformation<?> getTypeToMap(Object value, TypeInformation<?> type) {
if (type == null) {
type = ClassTypeInformation.OBJECT;
}
if (value == null) {
return type;
}
if (Enum.class.isInstance(value)) {
return ClassTypeInformation.from(((Enum<?>) value).getDeclaringClass());
}
return value.getClass().equals(type.getType()) ? type : ClassTypeInformation.from(value.getClass());
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.rest.webmvc.json;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -93,6 +93,7 @@ public class DomainObjectReaderUnitTests {
mappingContext.getPersistentEntity(Parent.class);
mappingContext.getPersistentEntity(Product.class);
mappingContext.getPersistentEntity(TransientReadOnlyProperty.class);
mappingContext.getPersistentEntity(CollectionOfEnumWithMethods.class);
mappingContext.afterPropertiesSet();
PersistentEntities entities = new PersistentEntities(Collections.singleton(mappingContext));
@@ -451,6 +452,20 @@ public class DomainObjectReaderUnitTests {
reader.readPut((ObjectNode) node, new TransientReadOnlyProperty(), mapper);
}
@Test // DATAREST-977
public void readsCollectionOfComplexEnum() throws Exception {
CollectionOfEnumWithMethods sample = new CollectionOfEnumWithMethods();
sample.enums.add(SampleEnum.FIRST);
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree("{ \"enums\" : [ \"SECOND\", \"FIRST\" ] }");
CollectionOfEnumWithMethods result = reader.merge((ObjectNode) node, sample, mapper);
assertThat(result.enums, contains(SampleEnum.SECOND, SampleEnum.FIRST));
}
@SuppressWarnings("unchecked")
private static <T> T as(Object source, Class<T> type) {
@@ -585,4 +600,33 @@ public class DomainObjectReaderUnitTests {
public void setName(String name) {}
}
// DATAREST-977
interface EnumInterface {
String getFoo();
}
static enum SampleEnum implements EnumInterface {
FIRST {
@Override
public String getFoo() {
return "first";
}
},
SECOND {
public String getFoo() {
return "second";
}
};
}
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
static class CollectionOfEnumWithMethods {
List<SampleEnum> enums = new ArrayList<SampleEnum>();
}
}