【问题标题】:How do you convert an Elasticsearch JSON String Response, with an Aggregation, to an Elasticsearch SearchResponse Object如何将带有聚合的 Elasticsearch JSON 字符串响应转换为 Elasticsearch SearchResponse 对象
【发布时间】:2018-09-22 17:32:23
【问题描述】:

我想将一个 json 字符串序列化为一个 Elasticsearch SearchResponse 对象。如果 json 字符串不包含聚合,它可以正常工作。

如果 json 字符串包含一个聚合,XContentParser 会抛出一个 ParsingException[Could not parse aggregation keyed as [target_field] 例外。

我用来将 json 字符串序列化为 Elasticsearch SearchResponse 对象的代码:

    Settings settings = Settings.builder().build();
    SearchModule searchModule = new SearchModule(settings, false, new ArrayList<>());

    NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(searchModule.getNamedXContents());

    JsonXContentParser xContentParser = new JsonXContentParser(xContentRegistry,
            new JsonFactory().createParser(json));
    SearchResponse response = SearchResponse.fromXContent(xContentParser);

似乎我必须将聚合注册到 NamedXContentRegistry,但我不知道该怎么做。

【问题讨论】:

  • 字符串是一个有效的查询吗?您可能想通过 kibana 的开发控制台运行它或通过 curl 尝试一下。
  • 是的。我已经测试过了。
  • 你有没有设法让它工作?
  • 我们最终为“”做了 sterms#distinct_values(参见elastic.co/guide/en/elasticsearch/reference/current/…)。我们发现聚合的类型没有被设置。另请参阅 discuss.elastic.co/t/…dadoonet 的回答@
  • 我遇到了同样的问题。我正在使用 ES 6.3,无法使用 Aaron 链接到的示例从 json 字符串构造 SearchResponse 对象。

标签: java json elasticsearch


【解决方案1】:

背景:
我是根据我为编写 Java 单元测试而创建 SearchResponse 对象的经验编写此答案的。目标是从 Elasticsearch 查询中获取任何 JSON 响应对象,将其编组到 SearchResponse 对象中,然后对创建可消耗输出的业务逻辑进行单元测试。

我们使用 Elasticsearch 6.7,高级别的客户端,并使用 Elastic 的 POJO 解析 SearchResponse(而不是只执行 .toString() 并使用 GSON 或 Jackson 操作它)。

解决方案说明:
Elasticsearch 的高级休息客户端通常解析来自低级休息客户端的结果。 SearchRequest 的响应 JSON 在 search 方法的第 129 行的 RestHighLevelClient 中转换为 SearchResponse 对象。此方法在第 1401 行调用performRequestAndParseEntity,它接受entityParser 作为CheckedFunction&lt;XContentParser, Resp, IOException&gt;。最后,我们可以看到,在第 1401 行调用 entityParser 时,它在第 1714 行调用了 parseEntity 方法,该方法确定了实体的 XContentType 并最终执行解析。值得注意的是,当在第 1726 行创建解析器时,会将 registry 传递给解析器。此注册表 包含响应字段可能的所有可能的 XContent 值。当 RestHighLevelClientline 288 上构建时,会创建 registryline 1748 列出了类型的完整列表,包括聚合类型。

解决方案:
在阅读了 Elasticsearch 对此的讨论后,如果您想将来自 Elastic 的 JSON 响应注入 SearchResponse 对象,则需要创建一个 NamedXContentRegistry 并列出XContents 测试您必须重新创建解析。一个辅助方法,sourced from Elastic's discussion

public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
    Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
    map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
    map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
    List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
            .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
            .collect(Collectors.toList());
  return entries;
}

上述代码中的地图需要有 ALL 的您的测试所需的聚合。有两个以上,为了简洁,这里有两个。

使用此帮助器 getNamedXContents() 方法,您现在可以使用以下方法获取 JSON 字符串并将其注入 SearchResponse。 Also sourced from Elastic's Discussion:

public static SearchResponse getSearchResponseFromJson(String jsonResponse){
    try {
        NamedXContentRegistry registry = new NamedXContentRegistry(getDefaultNamedXContents());
        XContentParser parser = JsonXContent.jsonXContent.createParser(registry, jsonResponse);
        return SearchResponse.fromXContent(parser);
    } catch (IOException e) {
        System.out.println("exception " + e);
    }catch (Exception e){
        System.out.println("exception " + e);
    }
    return new SearchResponse();
}

应用具有聚合结果的解决方案:
Elasticsearch 需要一个提示来知道将其解析为哪种类型的聚合。在向查询中添加 ?typed_keys 时,提示由 elastic 提供。在Aggregation Type Hints 上的 Elasticsearch 文档中显示了一个示例。

要将 JSON 字符串注入 SearchResponse 对象,必须 (1) 使用上述方法和 (2) 注入带有类型提示的字符串。

主要来源:

  1. https://discuss.elastic.co/t/elasticsearch-json-response-to-searchresponse-object/124394/6
  2. https://github.com/elastic/elasticsearch/blob/master/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java
  3. https://github.com/elastic/elasticsearch/blob/master/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java
  4. https://www.elastic.co/guide/en/elasticsearch/reference/current/returning-aggregation-type.html

注意:大约 2015 年有很多文章说这是不可能的。这显然是不正确的。

【讨论】:

  • 您好,我尝试按照您的建议进行操作,但是,聚合现在的类型为 ParsedStringTerms 而不是 StringTerms。您知道如何将其转换回 StringTerms 吗?
  • 您使用的是哪个版本的 Elasticsearch?它在此处列出:github.com/elastic/elasticsearch/blob/master/client/… 如果您将其添加到您的 getDefaultNamedXContents() 地图中,它会起作用吗?
  • 我使用的是 Elasticsearch 6.4.3。此外,ParsedStringTerms.fromXContent() 方法返回 ParsedStringTerm 对象而不是 StringTerm 对象。
  • 我看到了与@MinhKhoi 相同的行为。 ParsedStringTerms 不能转换为 StringTerm 对象。
【解决方案2】:

根据上面的答案,我设法做到了:

我写了一个这样的 JSON:

XContentBuilder builder = XContentFactory.jsonBuilder();
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
String result = Strings.toString(builder);

然后我设法像这样阅读它:

 try {
     NamedXContentRegistry registry = new NamedXContentRegistry(getDefaultNamedXContents());
     XContentParser parser = JsonXContent.jsonXContent.createParser(registry, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, result);
     SearchResponse searchResponse = SearchResponse.fromXContent(parser);
 } catch (IOException e) {
     System.out.println("exception " + e);
 } catch (Exception e) {
     System.out.println("exception " + e);
 }

public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
    Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
    map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
    map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
    List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
            .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
            .collect(Collectors.toList());
    return entries;
}

希望它有效:)

【讨论】:

    【解决方案3】:

    你需要在你的请求URL后面加上?typed_keys,比如/cranking/_search?typed_keys,看看这个reference

    您最好在NamedXContentRegistry 中添加更多parse 注册表,就像框架源代码一样。以下是所有注册表项:

    private List<NamedXContentRegistry.Entry> getProvidedNamedXContents() {
        List<NamedXContentRegistry.Entry> entries = new ArrayList<>();
    
        for (NamedXContentProvider service : ServiceLoader.load(NamedXContentProvider.class)) {
            entries.addAll(service.getNamedXContentParsers());
        }
    
        return entries;
    }
    
    private NamedXContentRegistry getDefaultNamedXContentRegistry() {
        List<NamedXContentRegistry.Entry> entries = new ArrayList<>();
        entries.addAll(getDefaultNamedXContents());
        entries.addAll(getProvidedNamedXContents());
        return new NamedXContentRegistry(entries);
    }
    
    
    private List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
        Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
        map.put("cardinality", (p, c) -> ParsedCardinality.fromXContent(p, (String) c));
        map.put("hdr_percentiles", (p, c) -> ParsedHDRPercentiles.fromXContent(p, (String) c));
        map.put("hdr_percentile_ranks", (p, c) -> ParsedHDRPercentileRanks.fromXContent(p, (String) c));
        map.put("tdigest_percentiles", (p, c) -> ParsedTDigestPercentiles.fromXContent(p, (String) c));
        map.put("tdigest_percentile_ranks", (p, c) -> ParsedTDigestPercentileRanks.fromXContent(p, (String) c));
        map.put("percentiles_bucket", (p, c) -> ParsedPercentilesBucket.fromXContent(p, (String) c));
        map.put("min", (p, c) -> ParsedMin.fromXContent(p, (String) c));
        map.put("max", (p, c) -> ParsedMax.fromXContent(p, (String) c));
        map.put("sum", (p, c) -> ParsedSum.fromXContent(p, (String) c));
        map.put("avg", (p, c) -> ParsedAvg.fromXContent(p, (String) c));
        map.put("value_count", (p, c) -> ParsedValueCount.fromXContent(p, (String) c));
        map.put("simple_value", (p, c) -> ParsedSimpleValue.fromXContent(p, (String) c));
        map.put("derivative", (p, c) -> ParsedDerivative.fromXContent(p, (String) c));
        map.put("bucket_metric_value", (p, c) -> ParsedBucketMetricValue.fromXContent(p, (String) c));
        map.put("stats", (p, c) -> ParsedStats.fromXContent(p, (String) c));
        map.put("stats_bucket", (p, c) -> ParsedStatsBucket.fromXContent(p, (String) c));
        map.put("extended_stats", (p, c) -> ParsedExtendedStats.fromXContent(p, (String) c));
        map.put("extended_stats_bucket", (p, c) -> ParsedExtendedStatsBucket.fromXContent(p, (String) c));
        map.put("geo_bounds", (p, c) -> ParsedGeoBounds.fromXContent(p, (String) c));
        map.put("geo_centroid", (p, c) -> ParsedGeoCentroid.fromXContent(p, (String) c));
        map.put("histogram", (p, c) -> ParsedHistogram.fromXContent(p, (String) c));
        map.put("date_histogram", (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c));
        map.put("sterms", (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
        map.put("lterms", (p, c) -> ParsedLongTerms.fromXContent(p, (String) c));
        map.put("dterms", (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c));
        map.put("missing", (p, c) -> ParsedMissing.fromXContent(p, (String) c));
        map.put("nested", (p, c) -> ParsedNested.fromXContent(p, (String) c));
        map.put("reverse_nested", (p, c) -> ParsedReverseNested.fromXContent(p, (String) c));
        map.put("global", (p, c) -> ParsedGlobal.fromXContent(p, (String) c));
        map.put("filter", (p, c) -> ParsedFilter.fromXContent(p, (String) c));
        map.put("sampler", (p, c) -> ParsedSampler.fromXContent(p, (String) c));
        map.put("geohash_grid", (p, c) -> ParsedGeoHashGrid.fromXContent(p, (String) c));
        map.put("range", (p, c) -> ParsedRange.fromXContent(p, (String) c));
        map.put("date_range", (p, c) -> ParsedDateRange.fromXContent(p, (String) c));
        map.put("geo_distance", (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
        map.put("filters", (p, c) -> ParsedFilters.fromXContent(p, (String) c));
        map.put("adjacency_matrix", (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c));
        map.put("siglterms", (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c));
        map.put("sigsterms", (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c));
        map.put("scripted_metric", (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c));
        map.put("ip_range", (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
        map.put("top_hits", (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
        map.put("composite", (p, c) -> ParsedComposite.fromXContent(p, (String) c));
        List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
                .map((entry) -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField((String) entry.getKey()), entry.getValue()))
                .collect(Collectors.toList());
        entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("term"), (parser, context) -> TermSuggestion.fromXContent(parser, (String) context)));
        entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("phrase"), (parser, context) -> PhraseSuggestion.fromXContent(parser, (String) context)));
        entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("completion"), (parser, context) -> CompletionSuggestion.fromXContent(parser, (String) context)));
        return entries;
    }
    

    【讨论】:

      【解决方案4】:

      我在使用 ElasticSearch 7.15 时遇到了同样的问题。上面技术专家给出的答案确实有助于解决这个问题,但它仍然对我不起作用,因为聚合体未被识别。 我的 JSON 中的聚合如下所示:

      {
        ...
        "aggregations": {
          "my-agg-name": {                 
            "buckets": []
          }
        }
      }
      

      就像this article 中解释的那样,问题是由于期望聚合在响应中以 sterms#my-agg-name 的形式返回,而原始 JSON 仅包含聚合的名称我的名字。 使用上面的代码并将相关的聚合类型添加到注册表中不起作用。

      我发现一个简单的解决方案是在响应中返回聚合类型。因此,按照the official documentation for the aggregation feature 中的示例,将 typed_key 添加到我的聚合请求中:

      GET /my-index-000001/_search?typed_keys
      {
        "aggs": {
          "my-agg-name": {
            "histogram": {
              "field": "my-field",
              "interval": 1000
            }
          }
        }
      } 
      

      将在响应中返回相关的聚合类型,如下所示(histogram#my-agg-name):

      {
        ...
        "aggregations": {
          "histogram#my-agg-name": {                 
            "buckets": []
          }
        }
      }
      

      现在解析器会识别出相关的聚合类型,转换就会成功。 如果没有,请确保答案中返回的聚合类型按照之前的答案包含在注册表映射中。 这适用于 ElasticSearch 7.15。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-01-17
        • 2022-01-21
        • 2016-04-01
        • 1970-01-01
        • 2017-08-13
        • 1970-01-01
        • 2022-11-19
        • 1970-01-01
        相关资源
        最近更新 更多