如何解决JSON对比复杂对象时null与空值视为不等的问题
解决复杂对象深度对比时Null与空值视为不等的问题
问题背景
我有两个复杂类对象需要判断是否相等,当前采用的步骤是:
- 使用Newtonsoft将对象转为JSON字符串
- 将JSON转换为JObject
- 调用
JObject.DeepEquals方法进行深度对比
但实际场景中,一个对象的部分属性为null,另一个对应属性为空值(比如空字符串、空集合),导致JObject.DeepEquals始终判定二者不等,而从业务逻辑上这两种情况应视为相等。需要找到方案统一处理null与空值,实现正确的相等判定。
示例类结构:
public class Example { public List<ClassA> classA { get; set; } public List<ClassB> classB { get; set; } public List<ClassC> ClassC { get; set; } public List<ClassD> classD { get; set; } public string identifier { get; set; } } public class ClassA { public int value { get; set; } public string text { get; set; } public DateTime date { get; set; } } public class ClassB { public string text1 { get; set; } public string text2 { get; set; } public DateTime date1 { get; set; } public List<CSettings> setting { get; set; } // 其他属性... }
解决方案
方案一:序列化时统一处理Null与空值
利用Newtonsoft.Json的序列化配置,将null值与空值统一为同一表现形式,再生成JObject进行对比。
自定义序列化配置
var serializerSettings = new JsonSerializerSettings { // 忽略null值,或设置为Populate填充默认值 NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Populate, Converters = new List<JsonConverter> { new NullCollectionConverter() } }; // 自定义转换器:将null集合转为空集合 public class NullCollectionConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return Activator.CreateInstance(objectType); } return serializer.Deserialize(reader, objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var collection = value as IEnumerable; if (collection == null || !collection.GetEnumerator().MoveNext()) { writer.WriteNull(); // 空集合序列化为null,也可改为序列化空集合 } else { serializer.Serialize(writer, value); } } }
执行对比
var jObj1 = JObject.FromObject(obj1, JsonSerializer.Create(serializerSettings)); var jObj2 = JObject.FromObject(obj2, JsonSerializer.Create(serializerSettings)); bool isEqual = JObject.DeepEquals(jObj1, jObj2);
方案二:提前标准化对象的Null/空值
在序列化前,递归遍历对象所有属性,将null转换为对应类型的空值(或反之),再执行原对比流程。
标准化方法实现
public static void NormalizeNullValues(object obj) { if (obj == null) return; var type = obj.GetType(); // 处理值类型与字符串 if (type.IsValueType || type == typeof(string)) { return; } // 处理集合 if (obj is IEnumerable enumerable) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { foreach (var item in enumerable) { NormalizeNullValues(item); } } return; } // 处理类属性 foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!prop.CanWrite) continue; var value = prop.GetValue(obj); if (value == null) { if (prop.PropertyType == typeof(string)) { prop.SetValue(obj, string.Empty); } else if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) { var emptyList = Activator.CreateInstance(prop.PropertyType); prop.SetValue(obj, emptyList); } // 可扩展处理其他类型,比如Dictionary等 } else { NormalizeNullValues(value); } } }
执行对比
// 先标准化两个对象 NormalizeNullValues(obj1); NormalizeNullValues(obj2); // 原流程对比 var jObj1 = JObject.FromObject(obj1); var jObj2 = JObject.FromObject(obj2); bool isEqual = JObject.DeepEquals(jObj1, jObj2);
方案三:自定义深度对比逻辑
跳过序列化步骤,直接递归对比对象属性,在对比时将null与对应空值视为相等。
自定义对比方法
public static bool DeepEqualsWithNullNormalization(object obj1, object obj2) { if (obj1 == obj2) return true; if (obj1 == null || obj2 == null) { return IsEmptyValue(obj1) && IsEmptyValue(obj2); } var type1 = obj1.GetType(); var type2 = obj2.GetType(); if (type1 != type2) return false; // 处理字符串:null与空字符串视为相等 if (type1 == typeof(string)) { return string.IsNullOrEmpty((string)obj1) && string.IsNullOrEmpty((string)obj2); } // 处理值类型 if (type1.IsValueType) { return Equals(obj1, obj2); } // 处理集合 if (obj1 is IEnumerable enum1 && obj2 is IEnumerable enum2) { var enum1Enumerator = enum1.GetEnumerator(); var enum2Enumerator = enum2.GetEnumerator(); while (enum1Enumerator.MoveNext()) { if (!enum2Enumerator.MoveNext()) return false; if (!DeepEqualsWithNullNormalization(enum1Enumerator.Current, enum2Enumerator.Current)) { return false; } } return !enum2Enumerator.MoveNext(); } // 处理类属性 foreach (var prop in type1.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var val1 = prop.GetValue(obj1); var val2 = prop.GetValue(obj2); if (!DeepEqualsWithNullNormalization(val1, val2)) { return false; } } return true; } // 判断是否为空值(null、空字符串、空集合等) private static bool IsEmptyValue(object obj) { if (obj == null) return true; if (obj is string str) return string.IsNullOrEmpty(str); if (obj is IEnumerable enumObj) return !enumObj.GetEnumerator().MoveNext(); // 值类型默认值是否视为空,可根据业务调整 return obj.Equals(Activator.CreateInstance(obj.GetType())); }
执行对比
bool isEqual = DeepEqualsWithNullNormalization(obj1, obj2);
内容的提问来源于stack exchange,提问作者Laxmanan S
相关产品推荐
相关产品推荐

