diff --git a/CHANGELOG.md b/CHANGELOG.md index 160ac76496..9c7fd5e31b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Fixed - Fixed Hit response when search request has storedFields as null ([#698](https://github.com/opensearch-project/opensearch-java/pull/698)) +- Fix InnerHits storedFields deserialization/serialization ([#781](https://github.com/opensearch-project/opensearch-java/pull/781) ### Security diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/core/search/InnerHits.java b/java-client/src/main/java/org/opensearch/client/opensearch/core/search/InnerHits.java index a99c9a4315..14ecb45568 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/core/search/InnerHits.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/core/search/InnerHits.java @@ -89,7 +89,7 @@ public class InnerHits implements JsonpSerializable { @Nullable private final SourceConfig source; - private final List storedField; + private final List storedFields; @Nullable private final Boolean trackScores; @@ -114,7 +114,7 @@ private InnerHits(Builder builder) { this.fields = ApiTypeHelper.unmodifiable(builder.fields); this.sort = ApiTypeHelper.unmodifiable(builder.sort); this.source = builder.source; - this.storedField = ApiTypeHelper.unmodifiable(builder.storedField); + this.storedFields = ApiTypeHelper.unmodifiable(builder.storedFields); this.trackScores = builder.trackScores; this.version = builder.version; @@ -225,10 +225,10 @@ public final SourceConfig source() { } /** - * API name: {@code stored_field} + * API name: {@code stored_fields} */ - public final List storedField() { - return this.storedField; + public final List storedFields() { + return this.storedFields; } /** @@ -344,10 +344,10 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { this.source.serialize(generator, mapper); } - if (ApiTypeHelper.isDefined(this.storedField)) { - generator.writeKey("stored_field"); + if (ApiTypeHelper.isDefined(this.storedFields)) { + generator.writeKey("stored_fields"); generator.writeStartArray(); - for (String item0 : this.storedField) { + for (String item0 : this.storedFields) { generator.write(item0); } @@ -414,7 +414,7 @@ public static class Builder extends ObjectBuilderBase implements ObjectBuilder storedField; + private List storedFields; @Nullable private Boolean trackScores; @@ -623,22 +623,22 @@ public final Builder source(Function - * Adds all elements of list to storedField. + * Adds all elements of list to storedFields. */ - public final Builder storedField(List list) { - this.storedField = _listAddAll(this.storedField, list); + public final Builder storedFields(List list) { + this.storedFields = _listAddAll(this.storedFields, list); return this; } /** - * API name: {@code stored_field} + * API name: {@code stored_fields} *

- * Adds one or more values to storedField. + * Adds one or more values to storedFields. */ - public final Builder storedField(String value, String... values) { - this.storedField = _listAdd(this.storedField, value, values); + public final Builder storedFields(String value, String... values) { + this.storedFields = _listAdd(this.storedFields, value, values); return this; } @@ -696,7 +696,7 @@ protected static void setupInnerHitsDeserializer(ObjectDeserializer indexTemplateMap = Map.of("name", "test", "index_patterns", "*", "create", true, "priority", 1); + final Map indexTemplateMap = new HashMap<>(); + indexTemplateMap.put("name", "test"); + indexTemplateMap.put("index_patterns", "*"); + indexTemplateMap.put("create", true); + indexTemplateMap.put("priority", 1); final String indexTemplate = new ObjectMapper().writeValueAsString(indexTemplateMap); - final var parser = mapper.jsonProvider().createParser(new StringReader(indexTemplate)); + final JsonParser parser = mapper.jsonProvider().createParser(new StringReader(indexTemplate)); final PutIndexTemplateRequest putIndexTemplateRequest = PutIndexTemplateRequest._DESERIALIZER.deserialize(parser, mapper); assertEquals(putIndexTemplateRequest.name(), "test"); - assertEquals(putIndexTemplateRequest.indexPatterns(), List.of("*")); + assertEquals(putIndexTemplateRequest.indexPatterns(), Collections.singletonList("*")); assertEquals((int) putIndexTemplateRequest.priority(), 1); } diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/core/search/InnerHitsTest.java b/java-client/src/test/java/org/opensearch/client/opensearch/core/search/InnerHitsTest.java new file mode 100644 index 0000000000..1269ebb0df --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/core/search/InnerHitsTest.java @@ -0,0 +1,75 @@ +package org.opensearch.client.opensearch.core.search; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import jakarta.json.stream.JsonGenerator; +import jakarta.json.stream.JsonParser; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; +import org.junit.Test; +import org.opensearch.client.json.JsonpMapper; +import org.opensearch.client.json.JsonpSerializable; +import org.opensearch.client.json.jsonb.JsonbJsonpMapper; +import org.opensearch.client.opensearch.core.SearchRequest; + +public class InnerHitsTest { + private final JsonpMapper mapper = new JsonbJsonpMapper(); + private final String storedSalary = "details.salary"; + private final String storedJobId = "details.jobId"; + + /** + * test if the json field for storedFields is generated with the correct name "stored_fields" + */ + @Test + public void testInnerHitStoredFields() { + InnerHits hits = InnerHits.of((it) -> it.storedFields(List.of("field1", "field2"))); + assertTrue(toJson(hits).contains("stored_fields")); + } + + /** + * test if the field "stored_fields" is present after deserialization/serialization + * of InnerHits + */ + @Test + public void testInnerHitFromParsed() { + JsonParser parser = mapper.jsonProvider().createParser(new StringReader(innerHitsJson)); + InnerHits innerHits = InnerHits._DESERIALIZER.deserialize(parser, mapper); + assertThat(innerHits.storedFields(), containsInAnyOrder(storedJobId, storedSalary)); + String actualJson = toJson(innerHits); + assertEquals(innerHitsJson, actualJson); + + } + + /** + * We test if the field "stored_fields" is present in the InnerHits after deserialization/serialization + * of a SearchRequest + */ + @Test + public void testRequestWithInnerHitFromParsed() { + JsonParser parser = mapper.jsonProvider().createParser(new StringReader(searchRequestJson)); + SearchRequest searchRequest = SearchRequest._DESERIALIZER.deserialize(parser, mapper); + InnerHits innerHits = searchRequest.query().bool().must().get(1).nested().innerHits(); + assertThat(innerHits.storedFields(), containsInAnyOrder(storedJobId, storedSalary)); + String actualJson = toJson(searchRequest); + assertEquals(searchRequestJson, actualJson); + } + + private String toJson(JsonpSerializable obj) { + StringWriter stringWriter = new StringWriter(); + try (JsonGenerator generator = mapper.jsonProvider().createGenerator(stringWriter)) { + mapper.serialize(obj, generator); + } + return stringWriter.toString(); + } + + private final String innerHitsJson = String.format("{\"_source\":false,\"stored_fields\":[\"%s\",\"%s\"]}", storedJobId, storedSalary); + private final String searchRequestJson = String.format( + "{\"_source\":false,\"query\":{\"bool\":{\"must\":[{\"match_all\":{}},{\"nested\":{\"inner_hits\":%s,\"path\":\"details\"," + + "\"query\":{\"match_all\":{}}}}]}},\"stored_fields\":[\"title\",\"companyName\"]}", + innerHitsJson + ); +}