如何实现基于动态自定义条件的通用JSON数组筛选功能
动态筛选JSON数组的两种实现方案
以下两种方案均支持任意数量的筛选条件,兼容字符串、数值、日期类型的比较判断,可直接适配你提到的逗号分隔条件输入格式。
方案1:动态生成JsonPath(实现成本最低)
通过解析输入的条件字符串,直接拼接生成标准JsonPath表达式,利用Newtonsoft.Json内置的JsonPath查询能力实现筛选,无需复杂的逻辑开发。
核心实现代码
using Newtonsoft.Json.Linq; public static JArray FilterJsonArray(string jsonArrayStr, string conditionString) { var jArray = JArray.Parse(jsonArrayStr); if (string.IsNullOrWhiteSpace(conditionString)) return jArray; // 支持的操作符,按长度降序排列,优先匹配长操作符避免拆分错误 var supportOperators = new List<string> { ">=", "<=", "!=", "=", ">", "<" }; // 输入的等于号映射为JsonPath标准的双等于号 var opMap = new Dictionary<string, string> { { "=", "==" } }; var conditionList = new List<string>(); foreach (var condItem in conditionString.Split(',', StringSplitOptions.RemoveEmptyEntries)) { var cleanedCond = condItem.Trim(); var op = supportOperators.First(o => cleanedCond.Contains(o)); var parts = cleanedCond.Split(new[] { op }, StringSplitOptions.RemoveEmptyEntries); var fieldName = parts[0].Trim(); var rawValue = parts[1].Trim().Trim('"', '\''); // 根据值类型生成对应格式的查询值 string formattedValue; if (DateTime.TryParse(rawValue, out DateTime dateValue)) { // 日期统一转成ISO格式避免解析错误 formattedValue = $"'{dateValue:yyyy-MM-dd HH:mm:ss}'"; } else if (double.TryParse(rawValue, out _)) { // 数值类型不需要加引号 formattedValue = rawValue; } else { // 字符串转义单引号 formattedValue = $"'{rawValue.Replace("'", "\\'")}'"; } var actualOp = opMap.TryGetValue(op, out var mappedOp) ? mappedOp : op; conditionList.Add($"@.{fieldName} {actualOp} {formattedValue}"); } // 拼接完整JsonPath表达式 var jsonPath = $"$.[?({string.Join(" && ", conditionList)})]"; return new JArray(jArray.SelectTokens(jsonPath)); }
调用示例
// 单条件筛选EmpID=29 var result1 = FilterJsonArray(员工JSON字符串, "EmpID=\"29\""); // 多条件筛选Age=23且EmpName=Jhon var result2 = FilterJsonArray(员工JSON字符串, "Age=\"23\",EmpName=\"Jhon\""); // 日期类型筛选BirthDate大于1995年5月12日 var result3 = FilterJsonArray(包含生日的JSON字符串, "BirthDate>1995-05-12");
本方案优势:代码量少、适配快,天然支持任意数量条件的组合;劣势为性能略低于LINQ方案,适合调用频率不高的场景。
方案2:动态构建LINQ表达式树(性能更优)
通过动态构建Lambda表达式树实现筛选,编译后的委托执行效率更高,且类型安全,不会出现字符串比较导致的逻辑错误,适合高频调用场景。
核心实现代码
using System.Linq.Expressions; using Newtonsoft.Json.Linq; public static List<JObject> FilterJsonArrayWithLinq(string jsonArrayStr, string conditionString) { var jArray = JArray.Parse(jsonArrayStr).OfType<JObject>().ToList(); if (string.IsNullOrWhiteSpace(conditionString)) return jArray; var supportOperators = new List<string> { ">=", "<=", "!=", "=", ">", "<" }; // 定义Lambda参数 x => ... var parameter = Expression.Parameter(typeof(JObject), "x"); // 初始条件为true Expression combinedCondition = Expression.Constant(true); foreach (var condItem in conditionString.Split(',', StringSplitOptions.RemoveEmptyEntries)) { var cleanedCond = condItem.Trim(); var op = supportOperators.First(o => cleanedCond.Contains(o)); var parts = cleanedCond.Split(new[] { op }, StringSplitOptions.RemoveEmptyEntries); var fieldName = parts[0].Trim(); var rawValue = parts[1].Trim().Trim('"', '\''); // 生成x["字段名"]的表达式 Expression fieldAccess = Expression.Property(parameter, "Item", Expression.Constant(fieldName)); Expression valueExp; Expression compareExp; // 根据值类型做对应的转换,避免字符串比较错误 if (DateTime.TryParse(rawValue, out DateTime dateVal)) { valueExp = Expression.Constant(dateVal); var convertMethod = typeof(JToken).GetMethod("ToObject", Type.EmptyTypes)!.MakeGenericMethod(typeof(DateTime)); fieldAccess = Expression.Call(fieldAccess, convertMethod); } else if (double.TryParse(rawValue, out double numVal)) { valueExp = Expression.Constant(numVal); var convertMethod = typeof(JToken).GetMethod("ToObject", Type.EmptyTypes)!.MakeGenericMethod(typeof(double)); fieldAccess = Expression.Call(fieldAccess, convertMethod); } else { valueExp = Expression.Constant(rawValue); var convertMethod = typeof(JToken).GetMethod("ToString", Type.EmptyTypes)!; fieldAccess = Expression.Call(fieldAccess, convertMethod); } // 生成对应操作符的比较表达式 compareExp = op switch { "=" => Expression.Equal(fieldAccess, valueExp), "!=" => Expression.NotEqual(fieldAccess, valueExp), ">" => Expression.GreaterThan(fieldAccess, valueExp), "<" => Expression.LessThan(fieldAccess, valueExp), ">=" => Expression.GreaterThanOrEqual(fieldAccess, valueExp), "<=" => Expression.LessThanOrEqual(fieldAccess, valueExp), _ => throw new NotSupportedException($"不支持的操作符:{op}") }; // 拼接多个条件 combinedCondition = Expression.AndAlso(combinedCondition, compareExp); } // 编译为可执行委托 var filterFunc = Expression.Lambda<Func<JObject, bool>>(combinedCondition, parameter).Compile(); return jArray.Where(filterFunc).ToList(); }
注意事项
- 条件解析时必须优先匹配长度更长的操作符(例如
>=要早于>匹配),避免拆分条件出错 - 字符串值如果包含特殊字符需要提前转义,避免生成的表达式语法错误
- 日期格式建议统一使用ISO标准格式(
yyyy-MM-dd、yyyy-MM-dd HH:mm:ss),减少日期解析错误概率
内容的提问来源于stack exchange,提问作者techresearch
相关产品推荐
相关产品推荐

