C#中使用LINQ筛选两个List<JObject>的集合差集
解决List基于业务字段的差集筛选问题
看起来你遇到的核心问题是默认的引用相等判断无法满足你的业务对比需求——你需要基于name、subLists里的canonicalForm和list集合来判断两个JObject是否“相同”,而不是单纯看对象引用是否一致。
为什么之前的方法无效?
- 双层循环逻辑错误:你的循环条件写错了(
i < comparedList.Count应该是j < comparedList.Count),而且直接比较baseList[i]["subLists"] != comparedList[j]["subLists"]是在比较对象引用,即使内容完全相同,不同实例也会返回true,导致错误添加大量重复项。 - Except方法默认行为:
Except默认使用引用相等比较,而JObject是引用类型,两个内容一致的不同JObject实例会被判定为不相等,所以直接用comparedList.Except(baseList)不会得到正确结果。
解决方案:基于业务逻辑的相等判断 + LINQ筛选
我们可以通过自定义相等判断逻辑,配合LINQ的Where或Except方法来实现需求。下面提供两种直观的实现方式:
方式一:直接在LINQ中编写判断逻辑(无需额外类)
先写一个辅助方法,用来判断两个JObject是否在业务上完全匹配:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; // 辅助方法:判断两个JObject是否业务相等 private static bool AreJObjectsMatching(JObject baseItem, JObject comparedItem) { // 1. 对比name字段 if (baseItem["name"].ToString() != comparedItem["name"].ToString()) return false; // 2. 对比subLists集合 var baseSubLists = baseItem["subLists"].ToObject<List<JObject>>(); var comparedSubLists = comparedItem["subLists"].ToObject<List<JObject>>(); if (baseSubLists.Count != comparedSubLists.Count) return false; foreach (var baseSub in baseSubLists) { var canonicalForm = baseSub["canonicalForm"].ToString(); var baseListItems = baseSub["list"].ToObject<List<string>>(); // 在对比列表的subLists中找对应canonicalForm的项 var matchingComparedSub = comparedSubLists.FirstOrDefault(s => s["canonicalForm"].ToString() == canonicalForm ); if (matchingComparedSub == null) return false; var comparedListItems = matchingComparedSub["list"].ToObject<List<string>>(); // 对比list集合(如果需要忽略大小写,可传入StringComparer.OrdinalIgnoreCase) if (!baseListItems.SequenceEqual(comparedListItems)) return false; } // 3. 对比roles字段(根据你的需求可选) var baseRoles = baseItem["roles"].ToObject<List<string>>(); var comparedRoles = comparedItem["roles"].ToObject<List<string>>(); if (!baseRoles.SequenceEqual(comparedRoles)) return false; return true; }
然后用LINQ筛选出对比列表中不在基础列表里的项:
// 反序列化你的JSON字符串 List<JObject> baseList = JsonConvert.DeserializeObject<List<JObject>>(baseListString); List<JObject> comparedList = JsonConvert.DeserializeObject<List<JObject>>(comparedListString); // 筛选差集:对比列表中不存在于基础列表的项 var finalResultList = comparedList .Where(comparedItem => !baseList.Any(baseItem => AreJObjectsMatching(baseItem, comparedItem))) .ToList(); // 输出结果(格式化JSON) var resultJson = JsonConvert.SerializeObject(finalResultList, Formatting.Indented); Console.WriteLine(resultJson);
方式二:自定义相等比较器配合Except方法
如果你更倾向于使用LINQ的Except方法,可以实现一个IEqualityComparer<JObject>:
public class JObjectBusinessEqualityComparer : IEqualityComparer<JObject> { public bool Equals(JObject x, JObject y) { if (x == null || y == null) return x == y; return AreJObjectsMatching(x, y); } public int GetHashCode(JObject obj) { // 基于业务字段计算哈希码,确保相等的对象哈希一致 int hash = obj["name"].ToString().GetHashCode(); var subLists = obj["subLists"].ToObject<List<JObject>>(); foreach (var sub in subLists) { hash ^= sub["canonicalForm"].ToString().GetHashCode(); foreach (var item in sub["list"].ToObject<List<string>>()) { hash ^= item.GetHashCode(); } } foreach (var role in obj["roles"].ToObject<List<string>>()) { hash ^= role.GetHashCode(); } return hash; } // 复用之前的AreJObjectsMatching方法 private static bool AreJObjectsMatching(JObject a, JObject b) { // 同方式一中的辅助方法实现 if (a["name"].ToString() != b["name"].ToString()) return false; var aSubLists = a["subLists"].ToObject<List<JObject>>(); var bSubLists = b["subLists"].ToObject<List<JObject>>(); if (aSubLists.Count != bSubLists.Count) return false; foreach (var aSub in aSubLists) { var canonicalForm = aSub["canonicalForm"].ToString(); var aList = aSub["list"].ToObject<List<string>>(); var matchingBSub = bSubLists.FirstOrDefault(s => s["canonicalForm"].ToString() == canonicalForm); if (matchingBSub == null) return false; var bList = matchingBSub["list"].ToObject<List<string>>(); if (!aList.SequenceEqual(bList)) return false; } var aRoles = a["roles"].ToObject<List<string>>(); var bRoles = b["roles"].ToObject<List<string>>(); if (!aRoles.SequenceEqual(bRoles)) return false; return true; } }
使用时直接传入Except方法:
var finalResultList = comparedList.Except(baseList, new JObjectBusinessEqualityComparer()).ToList();
验证结果
用你提供的测试数据运行后,会得到你期望的差集:
[ { "name": "competency", "subLists": [ { "canonicalForm": "c1", "list": [ "c one", "c 1", "cone", "Cone", "C ONE" ] }, { "canonicalForm": "c2", "list": [ "c two", "c 2" ] } ], "roles": [] }, { "name": "leavetype", "subLists": [ { "canonicalForm": "annual", "list": [ "Easter" ] } ], "roles": [] } ]
内容的提问来源于stack exchange,提问作者Kunal Mukherjee
相关产品推荐
相关产品推荐

