diff --git a/docs/loading-json.asciidoc b/docs/loading-json.asciidoc
index 2292b83f3..cecb7b2a8 100644
--- a/docs/loading-json.asciidoc
+++ b/docs/loading-json.asciidoc
@@ -30,10 +30,10 @@ You can create an index from that definition as follows:
["source","java"]
--------------------------------------------------
-include-tagged::{doc-tests}/LoadingJson.java[load-index]
+include-tagged::{doc-tests}/LoadingJsonTest.java[load-index]
--------------------------------------------------
-<1> the input stream for the resource file.
-<2> a {java-client} JSON mapper, used to create a JSON parser and find object deserializers. This will generally be the client's mapper.
+<1> open an input stream for the JSON resource file.
+<2> populate the index creation request with the resource file contents.
[discrete]
==== Ingesting documents from JSON files
@@ -42,9 +42,9 @@ Similarly, you can read documents to be stored in {es} from data files:
["source","java"]
--------------------------------------------------
-include-tagged::{doc-tests}/LoadingJson.java[ingest-data]
+include-tagged::{doc-tests}/LoadingJsonTest.java[ingest-data]
--------------------------------------------------
-<1> When calling `withJson()` on data structures that have generic type parameters, these generic types will be considered to be `JsonData`.
+<1> when calling `withJson()` on data structures that have generic type parameters, these generic types will be considered to be `JsonData`.
[discrete]
==== Creating a search request combining JSON and programmatic construction
@@ -53,7 +53,7 @@ You can combine `withJson()` with regular calls to setter methods. The example b
["source","java"]
--------------------------------------------------
-include-tagged::{doc-tests}/LoadingJson.java[query]
+include-tagged::{doc-tests}/LoadingJsonTest.java[query]
--------------------------------------------------
<1> loads the query from the JSON string.
<2> adds the aggregation.
@@ -66,7 +66,7 @@ The `withJson()` methods are partial deserializers: the properties loaded from t
["source","java"]
--------------------------------------------------
-include-tagged::{doc-tests}/LoadingJson.java[query-and-agg]
+include-tagged::{doc-tests}/LoadingJsonTest.java[query-and-agg]
--------------------------------------------------
<1> loads the query part of the request.
<2> loads the aggregation part of the request.
diff --git a/java-client/src/main/java/co/elastic/clients/json/SimpleJsonpMapper.java b/java-client/src/main/java/co/elastic/clients/json/SimpleJsonpMapper.java
new file mode 100644
index 000000000..c1cd35efd
--- /dev/null
+++ b/java-client/src/main/java/co/elastic/clients/json/SimpleJsonpMapper.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package co.elastic.clients.json;
+
+import jakarta.json.JsonException;
+import jakarta.json.spi.JsonProvider;
+import jakarta.json.stream.JsonGenerator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple implementation of JsonpMapper
that only handles classes of the Java API client.
+ *
+ * To handle application classes serialization and deserialization, consider using JacksonJsonpMapper
or
+ * JsonbJsonpMapper
.
+ */
+public class SimpleJsonpMapper extends JsonpMapperBase {
+
+ public static SimpleJsonpMapper INSTANCE = new SimpleJsonpMapper(true);
+ public static SimpleJsonpMapper INSTANCE_REJECT_UNKNOWN_FIELDS = new SimpleJsonpMapper(false);
+
+ private static final Map, JsonpSerializer>> serializers = new HashMap<>();
+ private static final Map, JsonpDeserializer>> deserializers = new HashMap<>();
+
+ static {
+ serializers.put(String.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Boolean.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Boolean.TYPE, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Integer.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Integer.TYPE, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Long.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Long.TYPE, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Float.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Float.TYPE, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Double.class, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+ serializers.put(Double.TYPE, (JsonpSerializer) (value, generator, mapper) -> generator.write(value));
+
+ deserializers.put(String.class, JsonpDeserializer.stringDeserializer());
+ deserializers.put(Boolean.class, JsonpDeserializer.booleanDeserializer());
+ deserializers.put(Boolean.TYPE, JsonpDeserializer.booleanDeserializer());
+ deserializers.put(Integer.class, JsonpDeserializer.integerDeserializer());
+ deserializers.put(Integer.TYPE, JsonpDeserializer.integerDeserializer());
+ deserializers.put(Long.class, JsonpDeserializer.longDeserializer());
+ deserializers.put(Long.TYPE, JsonpDeserializer.longDeserializer());
+ deserializers.put(Float.class, JsonpDeserializer.floatDeserializer());
+ deserializers.put(Float.TYPE, JsonpDeserializer.floatDeserializer());
+ deserializers.put(Double.class, JsonpDeserializer.doubleDeserializer());
+ deserializers.put(Double.TYPE, JsonpDeserializer.doubleDeserializer());
+ }
+
+ private final boolean ignoreUnknownFields;
+
+ public SimpleJsonpMapper(boolean ignoreUnknownFields) {
+ this.ignoreUnknownFields = ignoreUnknownFields;
+ }
+
+ public SimpleJsonpMapper() {
+ // Lenient by default
+ this(true);
+ }
+
+ @Override
+ public boolean ignoreUnknownFields() {
+ return ignoreUnknownFields;
+ }
+
+ @Override
+ public JsonProvider jsonProvider() {
+ return JsonpUtils.provider();
+ }
+
+ @Override
+ public void serialize(T value, JsonGenerator generator) {
+ JsonpSerializer serializer = findSerializer(value);
+
+ if (serializer == null) {
+ @SuppressWarnings("unchecked")
+ JsonpSerializer serializer_ = (JsonpSerializer)serializers.get(value.getClass());
+ serializer = serializer_;
+ }
+
+ if (serializer != null) {
+ serializer.serialize(value, generator, this);
+ } else {
+ throw new JsonException(
+ "Cannot find a serializer for type " + value.getClass().getName() +
+ ". Consider using a full-featured JsonpMapper"
+ );
+ }
+ }
+
+ @Override
+ protected JsonpDeserializer getDefaultDeserializer(Class clazz) {
+ @SuppressWarnings("unchecked")
+ JsonpDeserializer deserializer = (JsonpDeserializer) deserializers.get(clazz);
+ if (deserializer != null) {
+ return deserializer;
+ } else {
+ throw new JsonException(
+ "Cannot find a deserializer for type " + clazz.getName() +
+ ". Consider using a full-featured JsonpMapper"
+ );
+ }
+ }
+}
diff --git a/java-client/src/main/java/co/elastic/clients/json/WithJson.java b/java-client/src/main/java/co/elastic/clients/json/WithJson.java
index baccbdafe..c699c8bfd 100644
--- a/java-client/src/main/java/co/elastic/clients/json/WithJson.java
+++ b/java-client/src/main/java/co/elastic/clients/json/WithJson.java
@@ -35,11 +35,13 @@ public interface WithJson {
* This is a "partial deserialization": properties that were already set keep their value if they're not present in the JSON input,
* and properties can also be set after having called this method, including overriding those read from the JSON input.
*
- * @param parser the JSONP parser
- * @param mapper the JSONP mapper used to deserialize values and nested objects
+ * @param input the stream to read from
* @return this object
*/
- T withJson(JsonParser parser, JsonpMapper mapper);
+ default T withJson(InputStream input) {
+ JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
+ return withJson(mapper.jsonProvider().createParser(input), mapper);
+ }
/**
* Sets additional properties values on this object by reading from a JSON input.
@@ -47,11 +49,11 @@ public interface WithJson {
* This is a "partial deserialization": properties that were already set keep their value if they're not present in the JSON input,
* and properties can also be set after having called this method, including overriding those read from the JSON input.
*
- * @param input the stream to read from
- * @param mapper the JSONP mapper used to deserialize values and nested objects
+ * @param input the reader to read from
* @return this object
*/
- default T withJson(InputStream input, JsonpMapper mapper) {
+ default T withJson(Reader input) {
+ JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
return withJson(mapper.jsonProvider().createParser(input), mapper);
}
@@ -60,12 +62,13 @@ default T withJson(InputStream input, JsonpMapper mapper) {
*
* This is a "partial deserialization": properties that were already set keep their value if they're not present in the JSON input,
* and properties can also be set after having called this method, including overriding those read from the JSON input.
+ *
+ * This low level variant of withJson
gives full control on the json parser and object mapper. Most of the time
+ * using {@link #withJson(Reader)} and {@link #withJson(InputStream)} will be more convenient.
*
- * @param input the stream to read from
+ * @param parser the JSONP parser
* @param mapper the JSONP mapper used to deserialize values and nested objects
* @return this object
*/
- default T withJson(Reader input, JsonpMapper mapper) {
- return withJson(mapper.jsonProvider().createParser(input), mapper);
- }
+ T withJson(JsonParser parser, JsonpMapper mapper);
}
diff --git a/java-client/src/test/java/co/elastic/clients/documentation/LoadingJson.java b/java-client/src/test/java/co/elastic/clients/documentation/LoadingJsonTest.java
similarity index 62%
rename from java-client/src/test/java/co/elastic/clients/documentation/LoadingJson.java
rename to java-client/src/test/java/co/elastic/clients/documentation/LoadingJsonTest.java
index 78089943d..423074eb6 100644
--- a/java-client/src/test/java/co/elastic/clients/documentation/LoadingJson.java
+++ b/java-client/src/test/java/co/elastic/clients/documentation/LoadingJsonTest.java
@@ -24,6 +24,8 @@
import co.elastic.clients.elasticsearch._types.aggregations.CalendarInterval;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
+import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.model.ModelTestCase;
@@ -37,13 +39,30 @@
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Map;
-public class LoadingJson extends ModelTestCase {
+public class LoadingJsonTest extends ModelTestCase {
private DocTestsTransport transport = new DocTestsTransport();
private ElasticsearchClient client = new ElasticsearchClient(transport);
+ private static SearchResponse searchResponse = SearchResponse.searchResponseOf(b -> b
+ .aggregations(new HashMap<>())
+ .took(0)
+ .timedOut(false)
+ .hits(h -> h
+ .total(t -> t.value(0).relation(TotalHitsRelation.Eq))
+ .hits(new ArrayList<>())
+ )
+ .shards(s -> s
+ .total(1)
+ .failed(0)
+ .successful(1)
+ )
+ );
+
@Test
public void loadIndexDefinition() throws IOException {
@@ -54,15 +73,12 @@ public void loadIndexDefinition() throws IOException {
));
//tag::load-index
- InputStream jsonStream = this.getClass()
- .getResourceAsStream("some-index.json");
+ InputStream input = this.getClass()
+ .getResourceAsStream("some-index.json"); //<1>
CreateIndexRequest req = CreateIndexRequest.of(b -> b
.index("some-index")
- .withJson(
- jsonStream, //<1>
- client._jsonpMapper() //<2>
- )
+ .withJson(input) //<2>
);
boolean created = client.indices().create(req).acknowledged();
@@ -82,15 +98,17 @@ public void ingestDocument() throws IOException {
req = IndexRequest.of(b -> b
.index("some-index")
- .withJson(file, client._jsonpMapper())
+ .withJson(file)
);
client.index(req);
//end::ingest-data
}
-
+ @Test
public void query1() throws IOException {
+ transport.setResult(searchResponse);
+
//tag::query
Reader queryJson = new StringReader(
"{" +
@@ -102,9 +120,9 @@ public void query1() throws IOException {
" }" +
" }" +
"}");
-
- SearchRequest agg1 = SearchRequest.of(b -> b
- .withJson(queryJson, client._jsonpMapper()) //<1>
+
+ SearchRequest aggRequest = SearchRequest.of(b -> b
+ .withJson(queryJson) //<1>
.aggregations("max-cpu", a1 -> a1 //<2>
.dateHistogram(h -> h
.field("@timestamp")
@@ -117,38 +135,56 @@ public void query1() throws IOException {
.size(0)
);
- Map aggs1 = client
- .search(agg1, Void.class) //<3>
+ Map aggs = client
+ .search(aggRequest, Void.class) //<3>
.aggregations();
//end::query
-
+ }
+
+ @Test
+ public void query2() throws IOException {
+ transport.setResult(searchResponse);
+
//tag::query-and-agg
- Reader aggJson = new StringReader(
- "\"size\": 0, " +
- "\"aggs\": {" +
- " \"hours\": {" +
- " \"date_histogram\": {" +
- " \"field\": \"@timestamp\"," +
- " \"interval\": \"hour\"" +
- " }," +
- " \"aggs\": {" +
- " \"max-cpu\": {" +
- " \"max\": {" +
- " \"field\": \"host.cpu.usage\"" +
+ Reader queryJson = new StringReader(
+ "{" +
+ " \"query\": {" +
+ " \"range\": {" +
+ " \"@timestamp\": {" +
+ " \"gt\": \"now-1w\"" +
+ " }" +
+ " }" +
+ " }" +
+ "}");
+
+ Reader aggregationJson = new StringReader(
+ "{" +
+ " \"size\": 0, " +
+ " \"aggregations\": {" +
+ " \"hours\": {" +
+ " \"date_histogram\": {" +
+ " \"field\": \"@timestamp\"," +
+ " \"interval\": \"hour\"" +
+ " }," +
+ " \"aggregations\": {" +
+ " \"max-cpu\": {" +
+ " \"max\": {" +
+ " \"field\": \"host.cpu.usage\"" +
+ " }" +
" }" +
" }" +
" }" +
" }" +
"}");
-
- SearchRequest agg2 = SearchRequest.of(b -> b
- .withJson(queryJson, client._jsonpMapper()) //<1>
- .withJson(aggJson, client._jsonpMapper()) //<2>
+
+ SearchRequest aggRequest = SearchRequest.of(b -> b
+ .withJson(queryJson) //<1>
+ .withJson(aggregationJson) //<2>
.ignoreUnavailable(true) //<3>
);
- Map aggs2 = client
- .search(agg2, Void.class)
+ Map aggs = client
+ .search(aggRequest, Void.class)
.aggregations();
//end::query-and-agg
}
diff --git a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/ModelTestCase.java b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/ModelTestCase.java
index a7287309e..39e5a7cf9 100644
--- a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/ModelTestCase.java
+++ b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/ModelTestCase.java
@@ -21,6 +21,7 @@
import co.elastic.clients.json.JsonpDeserializer;
import co.elastic.clients.json.JsonpMapper;
+import co.elastic.clients.json.SimpleJsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.json.jsonb.JsonbJsonpMapper;
import jakarta.json.spi.JsonProvider;
@@ -45,22 +46,28 @@ public abstract class ModelTestCase extends Assert {
private JsonpMapper setupMapper(int rand) {
// Randomly choose json-b or jackson
- if (rand % 2 == 0) {
- System.out.println("Using a JsonB mapper (rand = " + rand + ").");
- return new JsonbJsonpMapper() {
- @Override
- public boolean ignoreUnknownFields() {
- return false;
- }
- };
- } else {
- System.out.println("Using a Jackson mapper (rand = " + rand + ").");
- return new JacksonJsonpMapper() {
- @Override
- public boolean ignoreUnknownFields() {
- return false;
- }
- };
+ switch(rand % 3) {
+ case 0:
+ System.out.println("Using a JsonB mapper (rand = " + rand + ").");
+ return new JsonbJsonpMapper() {
+ @Override
+ public boolean ignoreUnknownFields() {
+ return false;
+ }
+ };
+
+ case 1:
+ System.out.println("Using a Jackson mapper (rand = " + rand + ").");
+ return new JacksonJsonpMapper() {
+ @Override
+ public boolean ignoreUnknownFields() {
+ return false;
+ }
+ };
+
+ default:
+ System.out.println("Using a simple mapper (rand = " + rand + ").");
+ return SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
}
}
diff --git a/java-client/src/test/java/co/elastic/clients/json/WithJsonTest.java b/java-client/src/test/java/co/elastic/clients/json/WithJsonTest.java
index e4b8836ec..5d1770b4b 100644
--- a/java-client/src/test/java/co/elastic/clients/json/WithJsonTest.java
+++ b/java-client/src/test/java/co/elastic/clients/json/WithJsonTest.java
@@ -41,7 +41,7 @@ public void testRequestWithGenericValueBody() {
IndexRequest req = IndexRequest.of(b -> b
.index("index") // required path parameter (cannot be expressed in json)
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
assertEquals("index", req.index());
@@ -56,7 +56,7 @@ public void testRequestWithValueBody() {
"}";
PutIndicesSettingsRequest req = PutIndicesSettingsRequest.of(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
assertEquals(12, req.settings().analyzeMaxTokenCount().intValue());
@@ -67,7 +67,7 @@ public void testRegularObject() {
String json = "{\"field\": \"foo\", \"id\": 12}";
SlicedScroll s = SlicedScroll.of(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
.max(34) // required property not present in the json
);
@@ -109,7 +109,7 @@ public void testObjectWithGenericParam() {
// withJson() will read values of the generic parameter type as JsonData
SearchResponse r = SearchResponse.searchResponseOf(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
assertEquals(1, r.hits().total().value());
@@ -122,7 +122,7 @@ public void testTypeWithParent() {
String json = "{\"source\": \"return doc;\"}";
InlineScript is = InlineScript.of(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
assertEquals("return doc;", is.source());
@@ -140,7 +140,7 @@ public void testContainerTaggedUnion() {
" }";
Query q = Query.of(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
TermQuery tq = q.term();
@@ -162,7 +162,7 @@ public void testInternallyTaggedUnion() {
" }";
Property p = Property.of(b -> b
- .withJson(new StringReader(json), mapper)
+ .withJson(new StringReader(json))
);
TextProperty tp = p.text();