You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中反序列化Elasticsearch查询结果时如何排除字段?

问题:Spring Boot Elasticsearch 查询结果反序列化时按_source的includes/excludes过滤字段

我使用Spring Boot 2.6.14,有一个对应Elasticsearch索引文档的实体类MyEntity。希望执行搜索查询时,根据_source的includes和excludes选项返回特定字段,并将结果反序列化为MyEntity实例,但实际序列化结果不符合预期。

实体类代码

@Document(indexName = "index_name")
@JsonInclude
public class MyEntity {

  @JsonSetter("_id")
  private Long id; 

  @JsonSetter("field_1")
  private String fieldOne;

  @JsonSetter("field_2")
  private String fieldTwo;

  @JsonSetter("field_3")
  private String fieldThree; // 需要根据查询配置隐藏该字段
}

查询示例

查询1

POST index_name/_search
{
    "_source": {
        "includes": [ "_id", "field_1", "field_2" ],
        "excludes": [ "field_3" ]
    },
    "query": {
        "match" : { "field_1" : "value1" }
    }
}

查询2

POST index_name/_search
{
    "_source": {
        "includes": [ "_id", "field_1", "field_3" ],
        "excludes": [ "field_2" ]
    },
    "query": {
        "match" : { "field_1" : "value1" }
    }
}

索引文档示例

{
  "_index": "index_name",
  "_type": "_doc",
  "_id": "1",
  "_source": {
    "field_1": "value1",
    "field_2": null,
    "field_3": "value3"
  }
}

预期与实际结果

查询1预期结果

{
    "id": 1,
    "fieldOne": "value1",
    "fieldTwo": null
}

查询1实际结果

{
    "id": 1,
    "fieldOne": "value1",
    "fieldThree": null
}

查询2预期结果

{
    "id": 1,
    "fieldOne": "value1",
    "fieldThree": "value3"
}

查询2实际结果

{
    "id": 1,
    "fieldOne": "value1",
    "fieldTwo": null,
    "fieldThree": "value3"
}

解决方案

问题核心是:Jackson默认会序列化实体类的所有字段,无论该字段是否来自Elasticsearch的_source返回(未返回的字段会保持默认null值并被序列化)。需要让序列化结果仅保留Elasticsearch实际返回的字段,包括值为null的字段。

方案1:直接使用SearchHit的源数据Map构建响应

既然已经通过_source的includes/excludes过滤了字段,SearchHit.getSourceAsMap()已经包含了所有需要的字段,直接基于这个Map转换为目标格式即可:

SearchHits<MyEntity> searchHits = elasticsearchOperations.search(query, MyEntity.class);

List<Map<String, Object>> responseList = searchHits.stream()
        .map(hit -> {
            Map<String, Object> sourceMap = hit.getSourceAsMap();
            // 处理_id字段映射
            sourceMap.put("id", hit.getId());
            // 转换Elasticsearch字段名到实体类字段名
            sourceMap.put("fieldOne", sourceMap.remove("field_1"));
            sourceMap.put("fieldTwo", sourceMap.remove("field_2"));
            sourceMap.put("fieldThree", sourceMap.remove("field_3"));
            // 移除不存在的键(Elasticsearch未返回的字段会自动不存在)
            sourceMap.entrySet().removeIf(entry -> entry.getValue() == null && !sourceMap.containsKey(entry.getKey()));
            return sourceMap;
        })
        .collect(Collectors.toList());

这种方式完全贴合_source的过滤结果,不需要处理实体类的序列化规则,直接返回符合预期的结构。

方案2:自定义Jackson序列化过滤器动态过滤字段

如果必须返回MyEntity实例,可以通过Jackson的动态过滤器,根据查询的includes列表指定序列化字段:

// 1. 给实体类添加过滤器标记
@Document(indexName = "index_name")
@JsonFilter("myEntityFieldFilter")
public class MyEntity {
    // 原有字段定义不变
}

// 2. 执行查询并序列化时应用过滤器
List<String> includes = Arrays.asList("_id", "field_1", "field_2"); // 对应查询的includes参数
SearchHits<MyEntity> searchHits = elasticsearchOperations.search(query, MyEntity.class);

ObjectMapper mapper = new ObjectMapper();
// 构建实体类字段与Elasticsearch字段的映射
Map<String, String> fieldMapping = new HashMap<>();
fieldMapping.put("_id", "id");
fieldMapping.put("field_1", "fieldOne");
fieldMapping.put("field_2", "fieldTwo");
fieldMapping.put("field_3", "fieldThree");

// 转换includes到实体类字段名
Set<String> allowedFields = includes.stream()
        .map(fieldMapping::get)
        .collect(Collectors.toSet());

// 创建过滤器
FilterProvider filterProvider = new SimpleFilterProvider()
        .addFilter("myEntityFieldFilter", SimpleBeanPropertyFilter.filterOutAllExcept(allowedFields));

List<String> responseList = searchHits.stream()
        .map(hit -> {
            try {
                return mapper.writer(filterProvider).writeValueAsString(hit.getContent());
            } catch (JsonProcessingException e) {
                throw new RuntimeException("序列化失败", e);
            }
        })
        .collect(Collectors.toList());

方案3:使用Jackson的@JsonInclude结合非默认值配置

给实体类字段设置非null的默认值,然后使用@JsonInclude(JsonInclude.Include.NON_DEFAULT),这样只有被Elasticsearch显式赋值(包括null)的字段会被序列化:

@Document(indexName = "index_name")
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class MyEntity {

    @JsonSetter("_id")
    private Long id = -1; 

    @JsonSetter("field_1")
    private String fieldOne = "";

    @JsonSetter("field_2")
    private String fieldTwo = "";

    @JsonSetter("field_3")
    private String fieldThree = "";

    // 对应的getter/setter
}

注意:这种方法会改变字段的默认值,需要评估业务场景是否接受(比如id默认值-1可能不符合需求)。


内容的提问来源于stack exchange,提问作者Denis Kisina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:10:01