Skip to content

Commit

Permalink
Use a default mapper in withJson methods
Browse files Browse the repository at this point in the history
  • Loading branch information
swallez committed Mar 15, 2022
1 parent 3ded043 commit 78e8ee2
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 73 deletions.
14 changes: 7 additions & 7 deletions docs/loading-json.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <code>JsonpMapper</code> that only handles classes of the Java API client.
* <p>
* To handle application classes serialization and deserialization, consider using <code>JacksonJsonpMapper</code> or
* <code>JsonbJsonpMapper</code>.
*/
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<Class<?>, JsonpSerializer<?>> serializers = new HashMap<>();
private static final Map<Class<?>, JsonpDeserializer<?>> deserializers = new HashMap<>();

static {
serializers.put(String.class, (JsonpSerializer<String>) (value, generator, mapper) -> generator.write(value));
serializers.put(Boolean.class, (JsonpSerializer<Boolean>) (value, generator, mapper) -> generator.write(value));
serializers.put(Boolean.TYPE, (JsonpSerializer<Boolean>) (value, generator, mapper) -> generator.write(value));
serializers.put(Integer.class, (JsonpSerializer<Integer>) (value, generator, mapper) -> generator.write(value));
serializers.put(Integer.TYPE, (JsonpSerializer<Integer>) (value, generator, mapper) -> generator.write(value));
serializers.put(Long.class, (JsonpSerializer<Long>) (value, generator, mapper) -> generator.write(value));
serializers.put(Long.TYPE, (JsonpSerializer<Long>) (value, generator, mapper) -> generator.write(value));
serializers.put(Float.class, (JsonpSerializer<Float>) (value, generator, mapper) -> generator.write(value));
serializers.put(Float.TYPE, (JsonpSerializer<Float>) (value, generator, mapper) -> generator.write(value));
serializers.put(Double.class, (JsonpSerializer<Double>) (value, generator, mapper) -> generator.write(value));
serializers.put(Double.TYPE, (JsonpSerializer<Double>) (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 <T> void serialize(T value, JsonGenerator generator) {
JsonpSerializer<T> serializer = findSerializer(value);

if (serializer == null) {
@SuppressWarnings("unchecked")
JsonpSerializer<T> serializer_ = (JsonpSerializer<T>)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 <T> JsonpDeserializer<T> getDefaultDeserializer(Class<T> clazz) {
@SuppressWarnings("unchecked")
JsonpDeserializer<T> deserializer = (JsonpDeserializer<T>) 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"
);
}
}
}
23 changes: 13 additions & 10 deletions java-client/src/main/java/co/elastic/clients/json/WithJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,25 @@ public interface WithJson<T> {
* 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.
* <p>
* 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);
}

Expand All @@ -60,12 +62,13 @@ default T withJson(InputStream input, JsonpMapper mapper) {
* <p>
* 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.
* <p>
* This low level variant of <code>withJson</code> 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<JsonData> 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 {

Expand All @@ -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();
Expand All @@ -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(
"{" +
Expand All @@ -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")
Expand All @@ -117,38 +135,56 @@ public void query1() throws IOException {
.size(0)
);

Map<String, Aggregate> aggs1 = client
.search(agg1, Void.class) //<3>
Map<String, Aggregate> 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<String, Aggregate> aggs2 = client
.search(agg2, Void.class)
Map<String, Aggregate> aggs = client
.search(aggRequest, Void.class)
.aggregations();
//end::query-and-agg
}
Expand Down
Loading

0 comments on commit 78e8ee2

Please sign in to comment.