如何基于搜索条件列表过滤EmployeeDTO列表?修正逻辑及优化建议
员工列表过滤逻辑修正与优化建议
问题背景
需要基于一组SearchCriteria对EmployeeDTO列表实现过滤功能,支持普通字段、嵌套Map及数组类型的动态字段过滤,同时支持AND/OR/NOT逻辑运算。相关类结构与搜索条件示例如下:
核心类结构
public class EmployeeDTO { private String id; private String employeeId; private String email; private String phone; private String firstName; private String lastName; private String middleName; private LocalDate dob; private Map<String, Object> details; // 存储动态嵌套数据 } public class SearchCriteria { private String key; private String value; private SearchCondition condition; private SearchOperation operation; } public enum SearchCondition { EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUALS, LESS_THAN_OR_EQUALS } public enum SearchOperation { AND, OR, NOT }
搜索条件示例
[ { "key": "education.degree", // 对应details.education数组中每个元素的degree字段 "value": "BBA", "condition": "EQUALS" }, { "key": "education.degree", "value": "MBAA", "condition": "EQUALS", "operation": "AND" }, { "key": "education.degree", "value": "BA", "condition": "EQUALS", "operation": "OR" }, { "key": "middle_name", "value": "Anne", "condition": "EQUALS", "operation": "NOT" } ]
现有代码的核心问题
- 逻辑运算初始值与NOT处理错误:
- 初始
result设为true,若第一个条件为OR会导致逻辑错误; - NOT操作直接将
result替换为!match,未与之前的结果结合,不符合逻辑运算规则。
- 初始
- 嵌套数组字段取值逻辑错误:
getValue方法处理数组时,提取字段后直接返回列表,无法支持更深层级的嵌套路径;- 当数组元素非Map类型时直接返回null,未做兼容处理。
- 异常处理过于宽泛:
compareValues方法捕获所有异常并返回0,会隐藏类型转换错误,导致过滤结果异常。
- 字符串比较未考虑大小写:默认区分大小写,可能不符合业务需求。
修正后的完整代码
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import org.json.JSONObject; public class EmployeeFilter { private static final Logger log = LoggerFactory.getLogger(EmployeeFilter.class); private static final String OUTPUT_1 = "output_1"; private static final String OUTPUT_2 = "output_2"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public Map<String, JSONObject> processNode(List<EmployeeDTO> employees, List<SearchCriteria> searchCriteria) { Map<String, List<EmployeeDTO>> filteredEmployees = filterEmployees(employees, searchCriteria); JSONObject validEmployeesJson = new JSONObject(); validEmployeesJson.put(Constants.EMPLOYEES, filteredEmployees.get("valid")); JSONObject invalidEmployeesJson = new JSONObject(); invalidEmployeesJson.put(Constants.EMPLOYEES, filteredEmployees.get("invalid")); return Map.of( OUTPUT_1, validEmployeesJson, OUTPUT_2, invalidEmployeesJson ); } private Map<String, List<EmployeeDTO>> filterEmployees(List<EmployeeDTO> employees, List<SearchCriteria> searchCriteria) { Map<Boolean, List<EmployeeDTO>> partitionedEmployees = employees.stream() .collect(Collectors.partitioningBy(e -> isEmployeeMatchingSearchCriteria(e, searchCriteria))); return Map.of( "valid", partitionedEmployees.getOrDefault(true, Collections.emptyList()), "invalid", partitionedEmployees.getOrDefault(false, Collections.emptyList()) ); } private boolean isEmployeeMatchingSearchCriteria(EmployeeDTO employee, List<SearchCriteria> searchCriteria) { if (searchCriteria.isEmpty()) { return true; } // 初始化结果为第一个条件的匹配结果 boolean result = evaluateCondition(employee, searchCriteria.get(0)); // 从第二个条件开始遍历组合逻辑 for (int i = 1; i < searchCriteria.size(); i++) { SearchCriteria criterion = searchCriteria.get(i); boolean match = evaluateCondition(employee, criterion); SearchOperation operation = Optional.ofNullable(criterion.getOperation()).orElse(SearchOperation.AND); result = switch (operation) { case AND -> result && match; case OR -> result || match; case NOT -> result && !match; // NOT是对当前条件取反后与之前结果做AND }; } return result; } private boolean evaluateCondition(EmployeeDTO employee, SearchCriteria criterion) { String key = criterion.getKey(); String val = criterion.getValue(); SearchCondition condition = criterion.getCondition(); Object employeeValue = getValue(employee, key); if (employeeValue == null) { return false; } // 处理列表类型的字段 if (employeeValue instanceof List<?>) { List<?> list = (List<?>) employeeValue; return list.stream().anyMatch(item -> evaluateSingleValue(item, val, condition)); } return evaluateSingleValue(employeeValue, val, condition); } // 抽离单个值的条件判断逻辑 private boolean evaluateSingleValue(Object employeeValue, String val, SearchCondition condition) { switch (condition) { case EQUALS: // 支持字符串大小写忽略,可根据业务开关控制 if (employeeValue instanceof String && val instanceof String) { return ((String) employeeValue).equalsIgnoreCase(val); } return employeeValue.equals(val); case CONTAINS: return employeeValue.toString().toLowerCase().contains(val.toLowerCase()); case STARTS_WITH: return employeeValue.toString().toLowerCase().startsWith(val.toLowerCase()); case ENDS_WITH: return employeeValue.toString().toLowerCase().endsWith(val.toLowerCase()); case GREATER_THAN: return compareValues(employeeValue, val) > 0; case LESS_THAN: return compareValues(employeeValue, val) < 0; case GREATER_THAN_OR_EQUALS: return compareValues(employeeValue, val) >= 0; case LESS_THAN_OR_EQUALS: return compareValues(employeeValue, val) <= 0; default: return false; } } private int compareValues(Object employeeValue, String val) { try { if (employeeValue instanceof Number) { double employeeDouble = ((Number) employeeValue).doubleValue(); double valDouble = Double.parseDouble(val); return Double.compare(employeeDouble, valDouble); } else if (employeeValue instanceof String) { return ((String) employeeValue).compareToIgnoreCase(val); } else if (employeeValue instanceof LocalDate) { LocalDate employeeDate = (LocalDate) employeeValue; LocalDate valDate = LocalDate.parse(val, DATE_FORMATTER); return employeeDate.compareTo(valDate); } else { log.warn("Unsupported comparison type: {}", employeeValue.getClass().getName()); return 0; } } catch (NumberFormatException e) { log.error("Failed to parse number value: {}", val, e); return 0; } catch (Exception e) { log.error("Comparison failed for value: {} and target: {}", employeeValue, val, e); return 0; } } private Object getValue(EmployeeDTO employee, String key) { if (employee == null || key == null || key.isBlank()) { return null; } // 处理普通字段 switch (key) { case "employee_id": return employee.getEmployeeId(); case "email": return employee.getEmail(); case "first_name": return employee.getFirstName(); case "middle_name": return employee.getMiddleName(); case "last_name": return employee.getLastName(); case "phone": return employee.getPhone(); case "dob": return employee.getDob(); default: // 处理嵌套路径(支持数组+Map组合) Map<String, Object> details = employee.getDetails(); if (details == null) { return null; } String[] keyParts = key.split("\\."); Object currentValue = details; for (String part : keyParts) { if (currentValue == null) { break; } if (currentValue instanceof Map) { currentValue = ((Map<String, Object>) currentValue).get(part); } else if (currentValue instanceof List) { List<?> list = (List<?>) currentValue; // 遍历数组,提取每个元素的对应字段,收集非空值 List<Object> nestedValues = new ArrayList<>(); for (Object item : list) { if (item instanceof Map) { nestedValues.add(((Map<String, Object>) item).get(part)); } else { // 数组元素非Map类型,直接匹配是否等于当前part nestedValues.add(item); } } // 过滤空值,避免后续判断干扰 nestedValues.removeIf(Objects::isNull); currentValue = nestedValues.isEmpty() ? null : nestedValues; } else { // 当前值既不是Map也不是List,无法继续解析 currentValue = null; break; } } // 如果最终结果是只有一个元素的列表,直接返回单个值,简化后续判断 if (currentValue instanceof List && ((List<?>) currentValue).size() == 1) { return ((List<?>) currentValue).get(0); } return currentValue; } } }
关键优化点说明
- 逻辑运算修正:
- 初始化结果为第一个条件的匹配值,避免初始true导致的OR逻辑错误;
- NOT操作修正为
result && !match,表示当前条件不满足时才符合整体规则,符合业务逻辑。
- 嵌套字段取值优化:
- 支持数组元素为非Map类型的场景;
- 自动将单元素列表转换为单个值,简化后续条件判断;
- 增加空值过滤,避免无效值干扰。
- 条件判断优化:
- 抽离
evaluateSingleValue方法,复用单个值的判断逻辑; - 字符串比较默认忽略大小写,可根据业务需求调整;
- 细化异常捕获,增加日志输出,便于排查问题。
- 抽离
- 代码可读性提升:
- 使用
Optional处理默认操作符; - 提取日期格式化器为常量,避免重复创建;
- 增加空值边界判断,减少NPE风险。
- 使用
额外优化建议
- 使用Predicate组合:将每个SearchCriteria转换为
Predicate<EmployeeDTO>,通过逻辑组合实现过滤,代码更简洁且易维护; - 缓存路径解析:对频繁使用的key路径进行缓存,避免重复split和解析;
- 类型安全增强:为
details字段提供类型转换工具类,减少强制类型转换; - 单元测试覆盖:针对不同字段类型、逻辑运算、边界场景编写单元测试,确保过滤逻辑正确性;
- 配置化大小写规则:将字符串比较是否忽略大小写设为可配置参数,适配不同业务场景。
内容的提问来源于stack exchange,提问作者Shubham Gupta
相关产品推荐
相关产品推荐

