diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ece8b4d62..b68fb7b9d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ This section is for maintaining a changelog for all breaking changes for the cli - Added `queryImage` (query_image) field to `NeuralQuery`, following definition in ([Neural Query](https://opensearch.org/docs/latest/query-dsl/specialized/neural/)) ([#1137](https://github.com/opensearch-project/opensearch-java/pull/1138)) - Added `cancelAfterTimeInterval` to `SearchRequest` and `MsearchRequest` ([#1147](https://github.com/opensearch-project/opensearch-java/pull/1147)) - Added the `ml` namespace operations ([#1158](https://github.com/opensearch-project/opensearch-java/pull/1158)) +- Added `IndexTemplateMapping.Builder#withJson`, `SourceField.Builder#withJson` and `IndexSettings.Builder#withJson` for streamlining deserialization ([#1148](https://github.com/opensearch-project/opensearch-java/pull/1148)) ### Dependencies - Bumps `commons-logging:commons-logging` from 1.3.3 to 1.3.4 diff --git a/guides/json.md b/guides/json.md index 3f4acd42c8..74cbf826be 100644 --- a/guides/json.md +++ b/guides/json.md @@ -2,6 +2,9 @@ - [Serialization](#serialization) - [Using toJsonString](#using-tojsonstring) - [Manual Serialization](#manual-serialization) + - [Deserialization](#deserialization) + - [Using withJson](#using-withjson) + - [Using static _DESERIALIZER](#using-static-_deserializer) # Working With JSON @@ -50,4 +53,44 @@ private String toJson(JsonpSerializable object) { throw new UncheckedIOException(ex); } } +``` + +## Deserialization + +For demonstration let's consider an IndexTemplateMapping JSON String. + +```java + +String stringTemplate = + "{\"mappings\":{\"properties\":{\"age\":{\"type\":\"integer\"}}},\"settings\":{\"number_of_shards\":\"2\",\"number_of_replicas\":\"1\"}}"; +``` +### Using withJson +For classes, Builders of which implements `PlainDeserializable` interface, a default `withJson` method is provided. +The withJson method returns the Builder enabling you to chain Builder methods for additional configuration. +This implementation uses `jakarta.json.spi.JsonProvider` SPI to discover the available JSON provider instance +from the classpath and to create a new mapper. The `JsonpUtils` utility class streamlines this deserialization process. +The following code example demonstrates how to use the `withJson` method to deserialize objects: + +```java +InputStream inputStream = new ByteArrayInputStream(stringTemplate.getBytes(StandardCharsets.UTF_8)); +IndexTemplateMapping indexTemplateMapping = new IndexTemplateMapping.Builder().withJson(inputStream).build(); +``` + + +### Using static _DESERIALIZER +For classes annotated with `@JsonpDeserializable`, a static field _DESERIALIZER is provided, +which takes a mapper and a parser as arguments and returns the instance of the json value passed in the parser. +Notice that this way you cannot further customize the instance, the state of which will solely depend on the json value parsed. + +The following sample code demonstrates how to serialize an instance of a Java class: + +```java +private IndexTemplateMapping getInstance(String templateJsonString) { + InputStream inputStream = new ByteArrayInputStream(templateJsonString.getBytes(StandardCharsets.UTF_8)); + JsonbJsonpMapper mapper = new JsonbJsonpMapper(); + try (JsonParser parser = mapper.jsonProvider().createParser(inputStream)) { + IndexTemplateMapping indexTemplateMapping = new IndexTemplateMapping._DESERIALIZER.deserialize(parser, mapper); + return indexTemplateMapping; + } +} ``` \ No newline at end of file diff --git a/java-client/src/main/java/org/opensearch/client/json/PlainDeserializable.java b/java-client/src/main/java/org/opensearch/client/json/PlainDeserializable.java new file mode 100644 index 0000000000..d8890e2913 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/json/PlainDeserializable.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.json; + +import jakarta.json.stream.JsonParser; +import java.io.InputStream; +import java.io.Reader; + +/** Base interface to set JSON properties **/ + +public interface PlainDeserializable { + + B self(); + + /** Updates object with newly provided JSON properties + @param parser the JsonParser parser + @param mapper the JsonpMapper mapper used to deserialize values + @return this object + **/ + + default B withJson(JsonParser parser, JsonpMapper mapper) { + JsonpDeserializer deserializer = JsonpMapperBase.findDeserializer(this.getClass().getEnclosingClass()); + @SuppressWarnings("unchecked") + ObjectDeserializer objectDeserializer = (ObjectDeserializer) DelegatingDeserializer.unwrap(deserializer); + assert objectDeserializer != null; + return objectDeserializer.deserialize(self(), parser, mapper, parser.next()); + } + + /** Updates object with newly provided JSON properties + @param inputStream the stream to read from + @return this object + * **/ + default B withJson(InputStream inputStream) { + JsonpMapper defaultMapper = JsonpUtils.DEFAULT_JSONP_MAPPER; + try (JsonParser parser = defaultMapper.jsonProvider().createParser(inputStream)) { + return withJson(parser, defaultMapper); + } + } + + /** Updates object with newly provided JSON properties + @param reader the stream to read from + @return this object + * **/ + default B withJson(Reader reader) { + JsonpMapper defaultMapper = JsonpUtils.DEFAULT_JSONP_MAPPER; + try (JsonParser parser = defaultMapper.jsonProvider().createParser(reader)) { + return withJson(parser, defaultMapper); + } + } + +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/SourceField.java b/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/SourceField.java index 0fcaf11cf3..6ff76552f3 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/SourceField.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/SourceField.java @@ -41,6 +41,7 @@ import org.opensearch.client.json.JsonpMapper; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; +import org.opensearch.client.json.PlainDeserializable; import org.opensearch.client.json.PlainJsonSerializable; import org.opensearch.client.util.ApiTypeHelper; import org.opensearch.client.util.ObjectBuilder; @@ -172,7 +173,7 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { * Builder for {@link SourceField}. */ - public static class Builder extends ObjectBuilderBase implements ObjectBuilder { + public static class Builder extends ObjectBuilderBase implements ObjectBuilder, PlainDeserializable { @Nullable private Boolean compress; @@ -263,6 +264,11 @@ public SourceField build() { return new SourceField(this); } + + @Override + public Builder self() { + return this; + } } // --------------------------------------------------------------------------------------------- diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/TypeMapping.java b/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/TypeMapping.java index 7d8adceabf..cbc74c0013 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/TypeMapping.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/_types/mapping/TypeMapping.java @@ -43,6 +43,7 @@ import org.opensearch.client.json.JsonpMapper; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; +import org.opensearch.client.json.PlainDeserializable; import org.opensearch.client.json.PlainJsonSerializable; import org.opensearch.client.util.ApiTypeHelper; import org.opensearch.client.util.ObjectBuilder; @@ -363,7 +364,7 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { * Builder for {@link TypeMapping}. */ - public static class Builder extends ObjectBuilderBase implements ObjectBuilder { + public static class Builder extends ObjectBuilderBase implements ObjectBuilder, PlainDeserializable { @Nullable private AllField allField; @@ -660,6 +661,11 @@ public TypeMapping build() { return new TypeMapping(this); } + + @Override + public Builder self() { + return this; + } } // --------------------------------------------------------------------------------------------- diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/indices/IndexSettings.java b/java-client/src/main/java/org/opensearch/client/opensearch/indices/IndexSettings.java index 9e68b6fbcc..12cdd73aa0 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/indices/IndexSettings.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/indices/IndexSettings.java @@ -41,6 +41,7 @@ import org.opensearch.client.json.JsonpMapper; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; +import org.opensearch.client.json.PlainDeserializable; import org.opensearch.client.json.PlainJsonSerializable; import org.opensearch.client.opensearch._types.Time; import org.opensearch.client.util.ApiTypeHelper; @@ -1108,7 +1109,7 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { * Builder for {@link IndexSettings}. */ - public static class Builder extends ObjectBuilderBase implements ObjectBuilder { + public static class Builder extends ObjectBuilderBase implements ObjectBuilder, PlainDeserializable { @Nullable private IndexSettings index; @@ -1919,6 +1920,10 @@ public final Builder knnAlgoParamEfSearch(@Nullable Integer value) { return this; } + @Override + public Builder self() { + return this; + } } // --------------------------------------------------------------------------------------------- diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/indices/put_index_template/IndexTemplateMapping.java b/java-client/src/main/java/org/opensearch/client/opensearch/indices/put_index_template/IndexTemplateMapping.java index a32aac2ad9..638b5e8afa 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/indices/put_index_template/IndexTemplateMapping.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/indices/put_index_template/IndexTemplateMapping.java @@ -41,6 +41,7 @@ import org.opensearch.client.json.JsonpMapper; import org.opensearch.client.json.ObjectBuilderDeserializer; import org.opensearch.client.json.ObjectDeserializer; +import org.opensearch.client.json.PlainDeserializable; import org.opensearch.client.json.PlainJsonSerializable; import org.opensearch.client.opensearch._types.mapping.TypeMapping; import org.opensearch.client.opensearch.indices.Alias; @@ -139,7 +140,7 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { * Builder for {@link IndexTemplateMapping}. */ - public static class Builder extends ObjectBuilderBase implements ObjectBuilder { + public static class Builder extends ObjectBuilderBase implements ObjectBuilder, PlainDeserializable { @Nullable private Map aliases; @@ -219,6 +220,11 @@ public IndexTemplateMapping build() { return new IndexTemplateMapping(this); } + + @Override + public Builder self() { + return this; + } } // --------------------------------------------------------------------------------------------- diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainDeserializableTest.java b/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainDeserializableTest.java new file mode 100644 index 0000000000..d1f1faafc7 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainDeserializableTest.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.json; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.opensearch.client.opensearch.indices.PutIndexTemplateRequest; +import org.opensearch.client.opensearch.indices.put_index_template.IndexTemplateMapping; + +public class PlainDeserializableTest { + @Test + public void testWithJsonPutIndexTemplateRequest() { + + String stringTemplate = + "{\"mappings\":{\"properties\":{\"age\":{\"type\":\"integer\"}}},\"settings\":{\"number_of_shards\":\"2\",\"number_of_replicas\":\"1\"}}"; + InputStream inputStream = new ByteArrayInputStream(stringTemplate.getBytes(StandardCharsets.UTF_8)); + + PutIndexTemplateRequest indexTemplateRequest = new PutIndexTemplateRequest.Builder().name("My index") + .indexPatterns("index_pattern1") + .template(new IndexTemplateMapping.Builder().withJson(inputStream).build()) + .build(); + + String expectedName = "My index"; + List expectedIndexPatterns = Arrays.asList("index_pattern1"); + String expectedNumberOfShards = "2"; + + assertEquals(indexTemplateRequest.name(), expectedName); + assertEquals(expectedIndexPatterns, indexTemplateRequest.indexPatterns()); + assertEquals(expectedNumberOfShards, indexTemplateRequest.template().settings().numberOfShards()); + + } +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainJsonSerializableTest.java b/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainJsonSerializableTest.java index 7fbf6ec61c..8386dc04d5 100644 --- a/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainJsonSerializableTest.java +++ b/java-client/src/test/java/org/opensearch/client/opensearch/json/PlainJsonSerializableTest.java @@ -52,7 +52,7 @@ public void testIndexResponse() { // Test SearchRequest which implements PlainJsonSerializable @Test - public void testSearchResponse() { + public void testSearchRequest() { String expectedStringValue = "{\"aggregations\":{},\"query\":{\"match\":{\"name\":{\"query\":\"OpenSearch\"}}},\"terminate_after\":5}"; diff --git a/samples/src/main/java/org/opensearch/client/samples/json/DeserializationBasics.java b/samples/src/main/java/org/opensearch/client/samples/json/DeserializationBasics.java new file mode 100644 index 0000000000..b541369478 --- /dev/null +++ b/samples/src/main/java/org/opensearch/client/samples/json/DeserializationBasics.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.samples.json; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.client.opensearch.OpenSearchClient; +import org.opensearch.client.opensearch.indices.PutIndexTemplateRequest; +import org.opensearch.client.opensearch.indices.PutIndexTemplateResponse; +import org.opensearch.client.opensearch.indices.put_index_template.IndexTemplateMapping; +import org.opensearch.client.samples.SampleClient; +import org.opensearch.client.samples.Search; + +public class DeserializationBasics { + private static final Logger LOGGER = LogManager.getLogger(Search.class); + + private static OpenSearchClient client; + + public static void main(String[] args) { + try { + client = SampleClient.create(); + + var version = client.info().version(); + LOGGER.info("Server: {}@{}.", version.distribution(), version.number()); + + final var indexTemplateName = "my-index"; + + // Use Json String/File for storing template. + String stringTemplate = + "{\"mappings\":{\"properties\":{\"age\":{\"type\":\"integer\"}}},\"settings\":{\"number_of_shards\":\"2\",\"number_of_replicas\":\"1\"}}"; + // Create Input Stream for the above json template + InputStream inputStream = new ByteArrayInputStream(stringTemplate.getBytes(StandardCharsets.UTF_8)); + + // Create Index Template Request for index 'my-index'. + PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest.Builder().name(indexTemplateName) + .template(new IndexTemplateMapping.Builder().withJson(inputStream).build()) // Use the Builder.withJson method to + // deserialize the inputStream into an instance + // of the IndexTemplateMapping class. + .build(); + + LOGGER.info("Creating index template {}.", indexTemplateName); + + // Use toJsonString method to log Request and Response string. + LOGGER.debug("Index Template Request: {}.", putIndexTemplateRequest.toJsonString()); + PutIndexTemplateResponse response = client.indices().putIndexTemplate(putIndexTemplateRequest); + LOGGER.info("Index Template Response: {}.", response.toJsonString()); + + } catch (Exception e) { + LOGGER.error("Exception occurred.", e); + } + } +}