Optimize DocumentAdapters field conversion.
Convert search hit fields from JsonData through JSON-P values directly instead of building an intermediate JSON string and parsing it back into an EntityAsMap. This keeps parser-backed fields as regular Java Map/List/String/Number/Boolean/null values and avoids leaking JsonValue implementations into SearchDocument field access. Closes" #3178 Original Pull Request: #3311 Signed-off-by: 014-code <2402143478@qq.com>
This commit is contained in:
+64
-17
@@ -24,12 +24,18 @@ import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
import co.elastic.clients.elasticsearch.core.search.NestedIdentity;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import jakarta.json.JsonArray;
|
||||
import jakarta.json.JsonNumber;
|
||||
import jakarta.json.JsonObject;
|
||||
import jakarta.json.JsonString;
|
||||
import jakarta.json.JsonValue;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -85,22 +91,8 @@ final class DocumentAdapters {
|
||||
|
||||
Map<String, Double> matchedQueries = hit.matchedQueries();
|
||||
|
||||
Function<Map<String, JsonData>, EntityAsMap> fromFields = fields -> {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
final boolean[] firstField = { true };
|
||||
hit.fields().forEach((key, jsonData) -> {
|
||||
if (!firstField[0]) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('"').append(key).append("\":") //
|
||||
.append(jsonData.toJson(jsonpMapper).toString());
|
||||
firstField[0] = false;
|
||||
});
|
||||
sb.append('}');
|
||||
return new EntityAsMap().fromJson(sb.toString());
|
||||
};
|
||||
|
||||
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
|
||||
EntityAsMap hitFieldsAsMap = new EntityAsMap();
|
||||
hit.fields().forEach((key, jsonData) -> hitFieldsAsMap.put(key, toJavaObject(jsonData, jsonpMapper)));
|
||||
|
||||
Map<String, List<@Nullable Object>> documentFields = new LinkedHashMap<>();
|
||||
hitFieldsAsMap.forEach((key, value) -> {
|
||||
@@ -144,6 +136,61 @@ final class DocumentAdapters {
|
||||
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object toJavaObject(JsonData jsonData, JsonpMapper jsonpMapper) {
|
||||
return toJavaObject(jsonData.toJson(jsonpMapper));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object toJavaObject(JsonValue jsonValue) {
|
||||
|
||||
return switch (jsonValue.getValueType()) {
|
||||
case OBJECT -> toMap(jsonValue.asJsonObject());
|
||||
case ARRAY -> toList(jsonValue.asJsonArray());
|
||||
case STRING -> ((JsonString) jsonValue).getString();
|
||||
case NUMBER -> toNumber((JsonNumber) jsonValue);
|
||||
case TRUE -> Boolean.TRUE;
|
||||
case FALSE -> Boolean.FALSE;
|
||||
case NULL -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static Map<String, @Nullable Object> toMap(JsonObject jsonObject) {
|
||||
|
||||
Map<String, @Nullable Object> result = new LinkedHashMap<>();
|
||||
jsonObject.forEach((key, value) -> result.put(key, toJavaObject(value)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<@Nullable Object> toList(JsonArray jsonArray) {
|
||||
|
||||
List<@Nullable Object> result = new ArrayList<>(jsonArray.size());
|
||||
jsonArray.forEach(value -> result.add(toJavaObject(value)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Number toNumber(JsonNumber jsonNumber) {
|
||||
|
||||
if (!jsonNumber.isIntegral()) {
|
||||
return jsonNumber.doubleValue();
|
||||
}
|
||||
|
||||
try {
|
||||
return jsonNumber.intValueExact();
|
||||
} catch (ArithmeticException ignored) {
|
||||
// continue with a wider numeric type
|
||||
}
|
||||
|
||||
try {
|
||||
return jsonNumber.longValueExact();
|
||||
} catch (ArithmeticException ignored) {
|
||||
// continue with a wider numeric type
|
||||
}
|
||||
|
||||
BigInteger value = jsonNumber.bigIntegerValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
public static SearchDocument from(CompletionSuggestOption<EntityAsMap> completionSuggestOption) {
|
||||
|
||||
Document document = completionSuggestOption.source() != null ? Document.from(completionSuggestOption.source())
|
||||
|
||||
+44
@@ -19,6 +19,7 @@ import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import jakarta.json.JsonValue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -77,6 +78,49 @@ class DocumentAdaptersUnitTests {
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // #3178
|
||||
@DisplayName("should adapt parser backed search Hit fields to Java types")
|
||||
void shouldAdaptParserBackedSearchHitFieldsToJavaTypes() {
|
||||
|
||||
Hit<EntityAsMap> searchHit = new Hit.Builder<EntityAsMap>() //
|
||||
.index("index") //
|
||||
.id("my-id") //
|
||||
.fields("objectField", JsonData.fromJson(
|
||||
"{\"string\":\"value\",\"integer\":2,\"decimal\":1.5,\"nested\":{\"flag\":true}}")) //
|
||||
.fields("listField", JsonData.fromJson("[\"listValue\",{\"nested\":3},true,null]")) //
|
||||
.fields("stringField", JsonData.fromJson("\"stringValue\"")) //
|
||||
.build(); //
|
||||
|
||||
SearchDocument document = DocumentAdapters.from(searchHit, jsonpMapper);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
Object objectFieldValue = document.get("objectField");
|
||||
Object listFieldValue = document.get("listField");
|
||||
Object objectFieldFirstValue = document.getFieldValue("objectField");
|
||||
Object listFieldFirstValue = document.getFieldValue("listField");
|
||||
|
||||
softly.assertThat(objectFieldValue).isInstanceOf(Map.class).isNotInstanceOf(JsonValue.class);
|
||||
softly.assertThat(listFieldValue).isInstanceOf(List.class).isNotInstanceOf(JsonValue.class);
|
||||
softly.assertThat(document.get("stringField")).isEqualTo("stringValue");
|
||||
softly.assertThat(objectFieldFirstValue).isInstanceOf(Map.class).isNotInstanceOf(JsonValue.class);
|
||||
softly.assertThat(listFieldFirstValue).isEqualTo("listValue");
|
||||
|
||||
// noinspection unchecked
|
||||
Map<String, Object> objectField = (Map<String, Object>) objectFieldValue;
|
||||
softly.assertThat(objectField.get("string")).isEqualTo("value");
|
||||
softly.assertThat(objectField.get("integer")).isEqualTo(2);
|
||||
softly.assertThat(objectField.get("decimal")).isEqualTo(1.5d);
|
||||
softly.assertThat(objectField.get("nested")).isInstanceOf(Map.class).isNotInstanceOf(JsonValue.class);
|
||||
|
||||
// noinspection unchecked
|
||||
List<Object> listField = (List<Object>) listFieldValue;
|
||||
softly.assertThat(listField).containsExactly("listValue", Collections.singletonMap("nested", 3), true, null);
|
||||
softly.assertThat(listField.get(1)).isNotInstanceOf(JsonValue.class);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // #1973
|
||||
@DisplayName("should adapt search Hit from source")
|
||||
void shouldAdaptSearchHitFromSource() {
|
||||
|
||||
Reference in New Issue
Block a user