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

如何基于搜索条件列表过滤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"
    }
]

现有代码的核心问题

  1. 逻辑运算初始值与NOT处理错误:
    • 初始result设为true,若第一个条件为OR会导致逻辑错误;
    • NOT操作直接将result替换为!match,未与之前的结果结合,不符合逻辑运算规则。
  2. 嵌套数组字段取值逻辑错误:
    • getValue方法处理数组时,提取字段后直接返回列表,无法支持更深层级的嵌套路径;
    • 当数组元素非Map类型时直接返回null,未做兼容处理。
  3. 异常处理过于宽泛:
    • compareValues方法捕获所有异常并返回0,会隐藏类型转换错误,导致过滤结果异常。
  4. 字符串比较未考虑大小写:默认区分大小写,可能不符合业务需求。

修正后的完整代码

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;
        }
    }
}

关键优化点说明

  1. 逻辑运算修正:
    • 初始化结果为第一个条件的匹配值,避免初始true导致的OR逻辑错误;
    • NOT操作修正为result && !match,表示当前条件不满足时才符合整体规则,符合业务逻辑。
  2. 嵌套字段取值优化:
    • 支持数组元素为非Map类型的场景;
    • 自动将单元素列表转换为单个值,简化后续条件判断;
    • 增加空值过滤,避免无效值干扰。
  3. 条件判断优化:
    • 抽离evaluateSingleValue方法,复用单个值的判断逻辑;
    • 字符串比较默认忽略大小写,可根据业务需求调整;
    • 细化异常捕获,增加日志输出,便于排查问题。
  4. 代码可读性提升:
    • 使用Optional处理默认操作符;
    • 提取日期格式化器为常量,避免重复创建;
    • 增加空值边界判断,减少NPE风险。

额外优化建议

  • 使用Predicate组合:将每个SearchCriteria转换为Predicate<EmployeeDTO>,通过逻辑组合实现过滤,代码更简洁且易维护;
  • 缓存路径解析:对频繁使用的key路径进行缓存,避免重复split和解析;
  • 类型安全增强:为details字段提供类型转换工具类,减少强制类型转换;
  • 单元测试覆盖:针对不同字段类型、逻辑运算、边界场景编写单元测试,确保过滤逻辑正确性;
  • 配置化大小写规则:将字符串比较是否忽略大小写设为可配置参数,适配不同业务场景。

内容的提问来源于stack exchange,提问作者Shubham Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:05:55