C#中如何使用Json序列化对象时忽略指定属性(单元测试场景)
测试中忽略无关属性对比对象JSON的实用方案
我完全懂你的痛点!重构WPF初始化代码时,要对比新旧对象又不想污染主代码加特性,这种测试场景太常见了,但确实容易卡壳。我给你几个简单实用的方案,不用碰主类代码就能实现需求:
方法1:用Newtonsoft.Json自定义属性过滤(最成熟)
如果你项目里已经在用Newtonsoft.Json,可以写一个自定义的合约解析器,只序列化你指定的顶层属性,完全不用在主类加任何特性:
首先创建这个解析器类:
public class SelectedPropertiesContractResolver : DefaultContractResolver { private readonly HashSet<string> _allowedProperties; public SelectedPropertiesContractResolver(IEnumerable<string> allowedProperties) { _allowedProperties = new HashSet<string>(allowedProperties); } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { // 只保留我们指定的属性 return base.CreateProperties(type, memberSerialization) .Where(p => _allowedProperties.Contains(p.PropertyName)) .ToList(); } }
然后在测试代码里这么用:
// 第一步:定义你要保留的顶层属性列表 var importantProperties = new List<string> { "Id", "DisplayName", "InitializationState" }; // 序列化旧对象到gold文件(重构前的基准) var legacyObject = GetLegacyInitializedObject(); var serializerSettings = new JsonSerializerSettings { ContractResolver = new SelectedPropertiesContractResolver(importantProperties), Formatting = Formatting.Indented // 格式化后方便看差异 }; var legacyJson = JsonConvert.SerializeObject(legacyObject, serializerSettings); File.WriteAllText("gold.json", legacyJson); // 测试环节:序列化新方式初始化的对象 var newObject = GetNewInitializedObject(); var newJson = JsonConvert.SerializeObject(newObject, serializerSettings); // 直接对比JSON字符串完成验证 Assert.AreEqual(legacyJson, newJson);
方法2:手动提取属性生成字典(最直观,零学习成本)
如果不想搞JSON库的复杂逻辑,直接手动提取你关心的属性到字典,再序列化字典对比,完全符合你要的“顶层重要属性的字典”需求:
// 写一个工具方法提取指定属性 Dictionary<string, object> GetImportantPropertyDict(object obj, IEnumerable<string> propertyNames) { var propertyDict = new Dictionary<string, object>(); var objType = obj.GetType(); foreach (var propName in propertyNames) { var property = objType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance); if (property != null) { propertyDict[propName] = property.GetValue(obj); } } return propertyDict; } // 测试里用这个方法 var importantProperties = new List<string> { "Id", "DisplayName", "InitializationState" }; // 生成基准gold内容 var legacyDict = GetImportantPropertyDict(GetLegacyInitializedObject(), importantProperties); var legacyJson = JsonConvert.SerializeObject(legacyDict, Formatting.Indented); File.WriteAllText("gold.json", legacyJson); // 生成新对象的对比内容 var newDict = GetImportantPropertyDict(GetNewInitializedObject(), importantProperties); var newJson = JsonConvert.SerializeObject(newDict, Formatting.Indented); // 对比验证 Assert.AreEqual(legacyJson, newJson);
这个方法逻辑完全透明,你能精准控制每一个要保留的属性,适合层级简单的场景,调试起来也方便。
方法3:用System.Text.Json自定义转换器(.NET Core/.NET 5+)
如果你的项目用的是微软自带的System.Text.Json,也可以写个自定义转换器来过滤属性:
public class SelectedPropertiesConverter<T> : JsonConverter<T> { private readonly HashSet<string> _allowedProperties; public SelectedPropertiesConverter(IEnumerable<string> allowedProperties) { _allowedProperties = new HashSet<string>(allowedProperties); } // 测试场景只需要序列化,反序列化可以直接抛异常(或者按需实现) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotImplementedException("Deserialization isn't needed for this test scenario"); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { writer.WriteStartObject(); var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => _allowedProperties.Contains(p.Name)); foreach (var prop in properties) { writer.WritePropertyName(prop.Name); JsonSerializer.Serialize(writer, prop.GetValue(value), options); } writer.WriteEndObject(); } }
测试中的用法:
var importantProperties = new List<string> { "Id", "DisplayName", "InitializationState" }; var serializerOptions = new JsonSerializerOptions { WriteIndented = true, Converters = { new SelectedPropertiesConverter<YourObjectType>(importantProperties) } }; // 生成基准gold文件 var legacyJson = JsonSerializer.Serialize(GetLegacyInitializedObject(), serializerOptions); File.WriteAllText("gold.json", legacyJson); // 生成新对象JSON并对比 var newJson = JsonSerializer.Serialize(GetNewInitializedObject(), serializerOptions); Assert.AreEqual(legacyJson, newJson);
总结
- 如果你已经在用Newtonsoft.Json,方法1最省心,复用性强;
- 如果你想要最直观的控制,方法2是首选,代码简单易懂;
- 如果你用的是System.Text.Json生态,方法3完全适配。
这三个方案都不用修改主类代码,所有过滤逻辑都封装在测试代码里,完美匹配你的测试需求!
内容的提问来源于stack exchange,提问作者Terry Watts




