diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 2abe1ed3e7..88e8435dc5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -674,12 +674,13 @@ public T getPropertyValue(MongoPersistentProperty property) { } if (!documentField.getProperty().isMap() && sourceValue instanceof Document document) { - return new Document(document.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> { - if (isKeyword(entry.getKey())) { - return getMappedValue(documentField, entry.getValue()); + + return BsonUtils.mapValues(document, (key, val) -> { + if (isKeyword(key)) { + return getMappedValue(documentField, val); } - return entry.getValue(); - }))); + return val; + }); } return valueConverter.write(value, conversionContext); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java index 331cbb2e86..675d6063cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java @@ -20,9 +20,12 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.StringJoiner; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.StreamSupport; @@ -716,6 +719,23 @@ public static Collection asCollection(Object source) { return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); } + public static Document mapValues(Document source, BiFunction valueMapper) { + return mapEntries(source, Entry::getKey, entry -> valueMapper.apply(entry.getKey(), entry.getValue())); + } + + public static Document mapEntries(Document source, Function,String> keyMapper, Function,Object> valueMapper) { + + if(source.isEmpty()) { + return source; + } + + Map target = new LinkedHashMap<>(source.size(), 1f); + for(Entry entry : source.entrySet()) { + target.put(keyMapper.apply(entry), valueMapper.apply(entry)); + } + return new Document(target); + } + @Nullable private static String toJson(@Nullable Object value) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/BsonUtilsTest.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/BsonUtilsTest.java index 96a2dd92c7..62e9ba5b9e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/BsonUtilsTest.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/BsonUtilsTest.java @@ -29,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Stream; import org.bson.BsonArray; @@ -180,6 +181,52 @@ void resolveValueForField(FieldName fieldName, boolean exists) { } } + @Test + void retainsOrderWhenMappingValues() { + + Document source = new Document(); + source.append("z", "first-entry"); + source.append("a", "second-entry"); + source.append("0", "third-entry"); + source.append("9", "fourth-entry"); + + Document target = BsonUtils.mapValues(source, (key, value) -> value); + assertThat(source).isNotSameAs(target).containsExactlyEntriesOf(source); + } + + @Test + void retainsOrderWhenMappingKeys() { + + Document source = new Document(); + source.append("z", "first-entry"); + source.append("a", "second-entry"); + + Document target = BsonUtils.mapEntries(source, entry -> entry.getKey().toUpperCase(), Entry::getValue); + assertThat(target).containsExactly(Map.entry("Z", "first-entry"), Map.entry("A", "second-entry")); + } + + @Test + void appliesValueMapping() { + Document source = new Document(); + source.append("z", "first-entry"); + source.append("a", "second-entry"); + + Document target = BsonUtils.mapValues(source, + (key, value) -> new StringBuilder(value.toString()).reverse().toString()); + assertThat(target).containsValues("yrtne-tsrif", "yrtne-dnoces"); + } + + @Test + void appliesKeyMapping() { + + Document source = new Document(); + source.append("z", "first-entry"); + source.append("a", "second-entry"); + + Document target = BsonUtils.mapEntries(source, entry -> entry.getKey().toUpperCase(), Entry::getValue); + assertThat(target).containsKeys("Z", "A"); + } + static Stream fieldNames() { return Stream.of(// Arguments.of(FieldName.path("a"), true), //