1
0
mirror of synced 2026-08-05 16:37:04 +00:00

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:
林桉
2026-07-26 20:29:48 +08:00
committed by GitHub
parent 63f3a355d9
commit fd557bc45c
2 changed files with 108 additions and 17 deletions
@@ -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())
@@ -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() {