DATADOC-161 - MappingMongoConverter now supports nested Maps as well.

Extracted method to handle reading Maps which is now also able to recursively resolve nested maps by using the generics information.
This commit is contained in:
Oliver Gierke
2011-06-02 11:47:00 +02:00
parent e8b130691c
commit ac4b27159b
2 changed files with 86 additions and 27 deletions

View File

@@ -24,10 +24,10 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
@@ -41,6 +41,7 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.data.document.mongodb.MongoDbFactory;
@@ -333,9 +334,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (!dbo.containsField("_id") && null != idProperty) {
Object idObj = null;
Class<?>[] targetClasses = new Class<?>[]{ObjectId.class, Object.class};
for (int i = 0; i < targetClasses.length; i++) {
for (Class<?> targetClasse : targetClasses) {
try {
idObj = wrapper.getProperty(idProperty, targetClasses[i], useFieldAccessOnly);
idObj = wrapper.getProperty(idProperty, targetClasse, useFieldAccessOnly);
if (null != idObj) {
break;
}
@@ -610,9 +611,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return new DBRef(db, collection, id);
}
@SuppressWarnings({"unchecked"})
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx,
String spelExpr) {
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, String spelExpr) {
Object o;
if (null != spelExpr) {
@@ -637,27 +637,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
dbObj = ((DBRef) dbObj).fetch();
}
if (dbObj instanceof DBObject) {
if (prop.isMap() && dbObj instanceof DBObject) {
// We have to find a potentially stored class to be used first.
Class<?> toType = findTypeToBeUsed((DBObject) dbObj);
Map<Object, Object> m = new LinkedHashMap<Object, Object>();
for (Map.Entry<String, Object> entry : ((Map<String, Object>) ((DBObject) dbObj).toMap()).entrySet()) {
if (entry.getKey().equals(CUSTOM_TYPE_KEY)) {
continue;
}
Class<?> keyType = prop.getComponentType();
Object key = conversionService.convert(entry.getKey(), keyType);
if (null != entry.getValue() && entry.getValue() instanceof DBObject) {
m.put(key, read((null != toType ? toType : prop.getMapValueType()), (DBObject) entry.getValue()));
} else {
m.put(key, entry.getValue());
}
}
return m;
if (prop.isMap()) {
return readMap(prop.getTypeInformation(), (DBObject) dbObj);
} else if (prop.isArray() && dbObj instanceof BasicDBObject && ((DBObject) dbObj).keySet().size() == 0) {
// It's empty
return Array.newInstance(prop.getComponentType(), 0);
@@ -697,6 +678,51 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return o;
}
/**
* Reads the given {@link DBObject} into a {@link Map}. will recursively resolve nested {@link Map}s as well.
*
* @param type the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link DBObject}.
* @param dbObject
*
* @return
*/
@SuppressWarnings("unchecked")
private Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject) {
Assert.notNull(type);
Assert.isTrue(type.isMap());
Assert.notNull(dbObject);
Class<?> customMapType = findTypeToBeUsed(dbObject);
Class<?> mapType = customMapType == null ? Map.class : customMapType;
Map<Object, Object> map = CollectionFactory.createMap(mapType, dbObject.keySet().size());
Map<String, Object> sourceMap = dbObject.toMap();
for (Entry<String, Object> entry : sourceMap.entrySet()) {
if (entry.getKey().equals(CUSTOM_TYPE_KEY)) {
continue;
}
Class<?> keyType = type.getComponentType().getType();
Object key = conversionService.convert(entry.getKey(), keyType);
if (null != entry.getValue() && entry.getValue() instanceof DBObject) {
DBObject valueSource = (DBObject) entry.getValue();
TypeInformation<?> valueType = type.getMapValueType();
Object value = valueType.isMap() ? readMap(valueType, valueSource) : read(valueType, valueSource);
map.put(key, value);
} else {
map.put(key, entry.getValue());
}
}
return map;
}
/**
* Returns the type to be used to convert the DBObject given to. Will return {@literal null} if there's not type hint
* found in the {@link DBObject} or the type hint found can't be converted into a {@link Class} as the type might not

View File

@@ -23,7 +23,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -285,6 +287,33 @@ public class MappingMongoConverterUnitTests {
assertThat(read.locale, is(Locale.US));
}
/**
* @see DATADOC-161
*/
@Test
public void readsNestedMapsCorrectly() {
Map<String, String> secondLevel = new HashMap<String, String>();
secondLevel.put("key1", "value1");
secondLevel.put("key2", "value2");
Map<String, Map<String, String>> firstLevel = new HashMap<String, Map<String, String>>();
firstLevel.put("level1", secondLevel);
firstLevel.put("level2", secondLevel);
ClassWithNestedMaps maps = new ClassWithNestedMaps();
maps.nestedMaps = new LinkedHashMap<String, Map<String, Map<String, String>>>();
maps.nestedMaps.put("afield", firstLevel);
DBObject dbObject = new BasicDBObject();
converter.write(maps, dbObject);
ClassWithNestedMaps result = converter.read(ClassWithNestedMaps.class, dbObject);
Map<String, Map<String, Map<String, String>>> nestedMap = result.nestedMaps;
assertThat(nestedMap, is(notNullValue()));
assertThat(nestedMap.get("afield"), is(firstLevel));
}
class ClassWithEnumProperty {
SampleEnum sampleEnum;
@@ -314,6 +343,10 @@ public class MappingMongoConverterUnitTests {
Map<Locale, String> map;
}
class ClassWithNestedMaps {
Map<String, Map<String, Map<String, String>>> nestedMaps;
}
public static class BirthDateContainer {
LocalDate birthDate;
}