Skip to content

Fix conversion for Map/Collection like types having a converter registered. #3662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3660-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3660-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3660-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3660-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ protected ConversionContext getConversionContext(ObjectPath path) {

Assert.notNull(path, "ObjectPath must not be null");

return new ConversionContext(path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef,
return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef,
this::getPotentiallyConvertedSimpleRead);
}

Expand Down Expand Up @@ -1323,7 +1323,7 @@ protected Map<Object, Object> readMap(ConversionContext context, Bson bson, Type
}

Object value = entry.getValue();
map.put(key, context.convert(value, valueType));
map.put(key, value == null ? value : context.convert(value, valueType));
}

return map;
Expand Down Expand Up @@ -1970,17 +1970,19 @@ public org.springframework.data.util.TypeInformation<? extends S> specialize(Cla
*/
protected static class ConversionContext {

private final org.springframework.data.convert.CustomConversions conversions;
private final ObjectPath path;
private final ContainerValueConverter<Bson> documentConverter;
private final ContainerValueConverter<Collection<?>> collectionConverter;
private final ContainerValueConverter<Bson> mapConverter;
private final ContainerValueConverter<DBRef> dbRefConverter;
private final ValueConverter<Object> elementConverter;

ConversionContext(ObjectPath path, ContainerValueConverter<Bson> documentConverter,
ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path, ContainerValueConverter<Bson> documentConverter,
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter) {

this.conversions = customConversions;
this.path = path;
this.documentConverter = documentConverter;
this.collectionConverter = collectionConverter;
Expand All @@ -2001,6 +2003,10 @@ public <S extends Object> S convert(Object source, TypeInformation<? extends S>

Assert.notNull(typeHint, "TypeInformation must not be null");

if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) {
return (S) elementConverter.convert(source, typeHint);
}

if (source instanceof Collection) {

Class<?> rawType = typeHint.getType();
Expand Down Expand Up @@ -2046,7 +2052,7 @@ public ConversionContext withPath(ObjectPath currentPath) {

Assert.notNull(currentPath, "ObjectPath must not be null");

return new ConversionContext(currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter,
return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter,
elementConverter);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.test.util.ReflectionTestUtils;

import com.mongodb.BasicDBList;
Expand Down Expand Up @@ -2428,6 +2430,98 @@ void shouldUseMostConcreteCustomConversionTargetOnRead() {
verify(subTypeOfGenericTypeConverter).convert(eq(source));
}


@Test // GH-3660
void usesCustomConverterForMapTypesOnWrite() {


converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();

TypeImplementingMap source = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();

converter.write(source, target);

assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2);
}

@Test // GH-3660
void usesCustomConverterForTypesImplementingMapOnWrite() {

converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();

TypeImplementingMap source = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();

converter.write(source, target);

assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2);
}

@Test // GH-3660
void usesCustomConverterForTypesImplementingMapOnRead() {

converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new DocumentToTypeImplementingMapConverter());
}));
converter.afterPropertiesSet();

org.bson.Document source = new org.bson.Document("1st", "one")
.append("2nd", 2)
.append("_class", TypeImplementingMap.class.getName());

TypeImplementingMap target = converter.read(TypeImplementingMap.class, source);

assertThat(target).isEqualTo(new TypeImplementingMap("one", 2));
}

@Test // GH-3660
void usesCustomConverterForPropertiesUsingTypesThatImplementMapOnWrite() {

converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();

TypeWrappingTypeImplementingMap source = new TypeWrappingTypeImplementingMap();
source.typeImplementingMap = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();

converter.write(source, target);

assertThat(target).containsEntry("typeImplementingMap", new org.bson.Document("1st", "one").append("2nd", 2));
}

@Test // GH-3660
void usesCustomConverterForPropertiesUsingTypesImplementingMapOnRead() {

converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new DocumentToTypeImplementingMapConverter());
}));
converter.afterPropertiesSet();

org.bson.Document source = new org.bson.Document("typeImplementingMap",
new org.bson.Document("1st", "one")
.append("2nd", 2))
.append("_class", TypeWrappingTypeImplementingMap.class.getName());

TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source);

assertThat(target.typeImplementingMap).isEqualTo(new TypeImplementingMap("one", 2));
}


static class GenericType<T> {
T content;
}
Expand Down Expand Up @@ -2971,4 +3065,106 @@ public SubTypeOfGenericType convert(org.bson.Document source) {
return target;
}
}

@WritingConverter
static class TypeImplementingMapToDocumentConverter implements Converter<TypeImplementingMap, org.bson.Document> {

@Nullable
@Override
public org.bson.Document convert(TypeImplementingMap source) {
return new org.bson.Document("1st", source.val1).append("2nd", source.val2);
}
}

@ReadingConverter
static class DocumentToTypeImplementingMapConverter implements Converter<org.bson.Document, TypeImplementingMap> {

@Nullable
@Override
public TypeImplementingMap convert(org.bson.Document source) {
return new TypeImplementingMap(source.getString("1st"), source.getInteger("2nd"));
}
}

static class TypeWrappingTypeImplementingMap {

String id;
TypeImplementingMap typeImplementingMap;
}

@EqualsAndHashCode
static class TypeImplementingMap implements Map<String,String> {

String val1;
int val2;

public TypeImplementingMap(String val1, int val2) {
this.val1 = val1;
this.val2 = val2;
}

@Override
public int size() {
return 0;
}

@Override
public boolean isEmpty() {
return false;
}

@Override
public boolean containsKey(Object key) {
return false;
}

@Override
public boolean containsValue(Object value) {
return false;
}

@Override
public String get(Object key) {
return null;
}

@Nullable
@Override
public String put(String key, String value) {
return null;
}

@Override
public String remove(Object key) {
return null;
}

@Override
public void putAll(@NonNull Map<? extends String, ? extends String> m) {

}

@Override
public void clear() {

}

@NonNull
@Override
public Set<String> keySet() {
return null;
}

@NonNull
@Override
public Collection<String> values() {
return null;
}

@NonNull
@Override
public Set<Entry<String, String>> entrySet() {
return null;
}
}
}