From 19043f6ae5cca1839ef2b4506711443e639bd67c Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Mon, 14 Apr 2025 16:48:20 +0200 Subject: [PATCH] fix(clients): correctly deserialize SearchResult --- .../java/com/algolia/playground/Search.java | 28 +++++++++++---- specs/search/paths/search/search.yml | 2 ++ templates/java/oneof_interface.mustache | 34 +++++++++++++++++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/playground/java/src/main/java/com/algolia/playground/Search.java b/playground/java/src/main/java/com/algolia/playground/Search.java index ac30c474cd9..51d4c71ce3d 100644 --- a/playground/java/src/main/java/com/algolia/playground/Search.java +++ b/playground/java/src/main/java/com/algolia/playground/Search.java @@ -6,7 +6,6 @@ import io.github.cdimascio.dotenv.Dotenv; import java.util.Arrays; import java.util.List; -import java.util.Map; class Actor extends Hit { @@ -47,6 +46,7 @@ public static void main(String[] args) throws Exception { singleSearch(client, indexName, query); multiSearch(indexName, query, client); + multiSearchHits(indexName, query, client); client.close(); } @@ -73,13 +73,27 @@ private static void multiSearch(String indexName, String query, SearchClient cli searchMethodParams.setRequests(requests); var responses = client.search(searchMethodParams, Actor.class); - var results = responses.getResults(); System.out.println("-> Multi Index Search:"); - for (var result : results) { - var response = (SearchResponse) result; - for (var hit : response.getHits()) { - var record = (Map) hit; - System.out.println("> " + record.get("name")); + for (var result : responses.getResults()) { + for (var hit : ((SearchResponse) result).getHits()) { + System.out.println("> " + hit.name); + } + } + } + + private static void multiSearchHits(String indexName, String query, SearchClient client) { + var searchQuery = new SearchForHits() + .setIndexName(indexName) + .setQuery(query) + .addAttributesToSnippet("title") + .addAttributesToSnippet("alternative_titles"); + List requests = List.of(searchQuery); + // with searchForHits, all the types are known, no need to cast + var responses = client.searchForHits(requests, Actor.class); + System.out.println("-> Multi Index SearchForHits:"); + for (var result : responses) { + for (var hit : result.getHits()) { + System.out.println("> " + hit.name); } } } diff --git a/specs/search/paths/search/search.yml b/specs/search/paths/search/search.yml index 8bbe9af42e2..688232c4948 100644 --- a/specs/search/paths/search/search.yml +++ b/specs/search/paths/search/search.yml @@ -15,6 +15,8 @@ post: - Different indices for different purposes, such as, one index for products, another one for marketing content. - Multiple searches to the same index—for example, with different filters. + + Use the helper `searchForHits` or `searchForFacets` to get the results in a more convenient format, if you already know the return type you want. requestBody: required: true description: Muli-search request body. Results are returned in the same order as the requests. diff --git a/templates/java/oneof_interface.mustache b/templates/java/oneof_interface.mustache index ea9ee77bc6c..751765c1a22 100644 --- a/templates/java/oneof_interface.mustache +++ b/templates/java/oneof_interface.mustache @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.annotation.*; import com.fasterxml.jackson.core.type.TypeReference; import com.algolia.exceptions.AlgoliaRuntimeException; import com.algolia.utils.CompoundType; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.BeanProperty; import java.io.IOException; import java.util.List; @@ -87,19 +89,45 @@ public interface {{classname}}{{#vendorExtensions.x-has-child-generic}}{{/ven {{/isModel}} {{/composedSchemas.oneOf}} - class Deserializer{{#vendorExtensions.x-has-child-generic}}{{/vendorExtensions.x-has-child-generic}} extends JsonDeserializer<{{classname}}{{#vendorExtensions.x-has-child-generic}}{{/vendorExtensions.x-has-child-generic}}> { + class Deserializer{{#vendorExtensions.x-has-child-generic}}{{/vendorExtensions.x-has-child-generic}} extends JsonDeserializer<{{classname}}{{#vendorExtensions.x-has-child-generic}}{{/vendorExtensions.x-has-child-generic}}>{{#vendorExtensions.x-has-child-generic}} implements ContextualDeserializer{{/vendorExtensions.x-has-child-generic}} { private static final Logger LOGGER = Logger.getLogger(Deserializer.class.getName()); + {{#vendorExtensions.x-has-child-generic}} + private JavaType returnType; + + public Deserializer() {} + + private Deserializer(JavaType returnType) { + this.returnType = returnType; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + JavaType contextualType = ctxt.getContextualType().containedType(0); + return new Deserializer(contextualType); + } + {{/vendorExtensions.x-has-child-generic}} + @Override public {{classname}}{{#vendorExtensions.x-has-child-generic}}{{/vendorExtensions.x-has-child-generic}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode tree = jp.readValueAsTree(); {{#composedSchemas.oneOf}} // deserialize {{{datatypeWithEnum}}} if (tree.{{#isModel}}isObject(){{#vendorExtensions.x-discriminator-fields}} && tree.has("{{{.}}}"){{/vendorExtensions.x-discriminator-fields}}{{/isModel}}{{#isEnumRef}}isTextual(){{/isEnumRef}}{{#isArray}}isArray(){{/isArray}}{{#isInteger}}isInt(){{/isInteger}}{{#isLong}}isLong(){{/isLong}}{{#isDouble}}isDouble(){{/isDouble}}{{#isBoolean}}isBoolean(){{/isBoolean}}{{#isString}}isTextual(){{/isString}}{{^isEnumRef}}{{^isModel}}{{^isArray}}{{^isInteger}}{{^isLong}}{{^isDouble}}{{^isBoolean}}{{^isString}}isObject(){{/isString}}{{/isBoolean}}{{/isDouble}}{{/isLong}}{{/isInteger}}{{/isArray}}{{/isModel}}{{/isEnumRef}}){ - try(JsonParser parser = tree.traverse(jp.getCodec())) { + try(JsonParser parser = tree.traverse(jp.getCodec())) { {{#isModel}} - return parser.readValueAs({{#vendorExtensions.x-has-child-generic}}new TypeReference<{{datatypeWithEnum}}>() {}{{/vendorExtensions.x-has-child-generic}}{{^vendorExtensions.x-has-child-generic}}{{{datatypeWithEnum}}}.class{{/vendorExtensions.x-has-child-generic}}); + {{#vendorExtensions.x-has-child-generic}} + // For generic types, the innerType is erased by Java, we need to use the contextual type. + JavaType innerType = ctxt.getTypeFactory().constructParametricType({{dataType}}.class, returnType); + if (parser.getCurrentToken() == null) { + parser.nextToken(); + } + return ctxt.readValue(parser, innerType); + {{/vendorExtensions.x-has-child-generic}} + {{^vendorExtensions.x-has-child-generic}} + return parser.readValueAs({{{datatypeWithEnum}}}.class); + {{/vendorExtensions.x-has-child-generic}} {{/isModel}} {{#isEnumRef}} return parser.readValueAs({{{datatypeWithEnum}}}.class);