You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动