如何根据搜索条件列表过滤 DTO 列表?

问题描述 投票:0回答:1

我正在尝试对 EmployeeDTO 列表实施过滤。我有一个 SearchCritera 列表,我想将其强加到列表中进行过滤。这是类结构。

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; // for storing dynamic nested data
}

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
}

您可以预期 SearchCriteria 列表如下所示:

[
    {
        "key": "education.degree", 
        // would reside in details.education.degree where education is an array of {degree, city}
        "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"
    }
]

这是我用来过滤员工列表的方法。但基于这种过滤技术,我得到的结果不准确。我一定是哪里出错了。

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

    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) {
        boolean result = true;

        for (SearchCriteria criterion : searchCriteria) {
            boolean match = evaluateCondition(employee, criterion);

            SearchOperation currentOperation = criterion.getOperation();
            if (currentOperation == null) currentOperation = SearchOperation.AND;

            result = switch(currentOperation) {
                case AND -> result && match;
                case OR -> result || match;
                case NOT -> !match;
            };
        }

        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;

        switch (condition) {
            case EQUALS:
                if (employeeValue instanceof List) {
                    List<?> list = (List<?>) employeeValue;
                    return list.stream().anyMatch(item -> item.equals(val));
                }
                return employeeValue.equals(val);
            
            case CONTAINS:
                return employeeValue.toString().contains(val);
            case STARTS_WITH:
                return employeeValue.toString().startsWith(val);
            case ENDS_WITH:
                return employeeValue.toString().endsWith(val);
            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).compareTo(val);
            } else if (employeeValue instanceof LocalDate) {
                LocalDate employeeDate = (LocalDate) employeeValue;
                LocalDate valDate = LocalDate.parse(val, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                return employeeDate.compareTo(valDate);
            } else {
                return 0;
            }
        } catch(Exception e) {
            return 0;
        }
    }

    private Object getValue(EmployeeDTO employee, String key) {
        if (employee == null || key == null) { // Added null check for employee
            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:
                // Handle dot notation (for nested objects), including arrays
                Map<String, Object> details = employee.getDetails();
                String[] keys = key.split("\\.");
                Object value = details;

                for (String part: keys) {
                    if (value instanceof Map) {
                        value = ((Map<String, Object>) value).get(part); // navigating the map
                    } else if (value instanceof List) {
                        List<?> list = (List<?>) value;
                        List<Object> matchedValues = new ArrayList<>();
                        
                        for (Object item: list) {
                            if(item instanceof Map) {
                                Object nestedValue = ((Map<String, Object>) item).get(part);
                                if (nestedValue != null) {
                                    matchedValues.add(nestedValue);
                                }
                            }
                        }

                        value = matchedValues; 
                    } else {
                        return null; // neither a map nor a list
                    }

                    // If it is nested further
                    if (value != null && !part.equals(keys[keys.length - 1])) {
                        continue;
                    } else {
                        // Found deepest value
                        return value;
                    }
                }
                return null;
        }
    }
}

请帮助我纠正逻辑,如果您能建议我可以进行一些代码改进,这也会很有帮助。预先感谢!

java spring filter logic
1个回答
0
投票

由于您在评论中澄清了您的

NOT
运算符是“与非”的意思,所以所提供的代码错误地处理了该运算符。 这个...

                case NOT -> !match;

...仅考虑当前项的值,忽略该点之前的运行结果。 这并不是“与非”的忠实执行。 你似乎想要...

                case NOT -> result && !match;

...相反。

© www.soinside.com 2019 - 2024. All rights reserved.