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
相关产品推荐
相关产品推荐

