.NET 4.6.2中序列化不可修改子类构成的对象图并忽略指定成员的最佳策略
.NET 4.6.2中序列化不可修改子类构成的对象图并忽略指定成员的最佳策略
针对你遇到的这种无法直接修改基类/派生类代码,但需要忽略特定成员序列化的场景,最稳妥且高效的方案是使用Newtonsoft.Json的自定义ContractResolver(假设你用的是Newtonsoft.Json,这是.NET 4.6.2生态中最常用的JSON序列化库)。先帮你梳理下之前尝试踩坑的原因,再给出可行的解决思路:
为什么之前的尝试出了问题?
- MetadataType属性不生效:Newtonsoft.Json默认不会识别
MetadataType标注的元数据类,你需要额外编写复杂逻辑让序列化器读取这些元数据,反而得不偿失。 - JsonConverter的递归陷阱:你遇到的栈溢出,本质是因为在
WriteJson中调用默认序列化时,当前的Converter又被重复应用,导致无限递归。如果要走Converter路线,必须手动规避递归,但这会增加实现复杂度。
推荐方案:自定义ContractResolver
ContractResolver是Newtonsoft.Json中控制序列化规则的核心组件,它可以全局定义哪些成员需要序列化/忽略,不需要修改原类代码,也不会有递归风险。
具体实现
- 创建自定义ContractResolver,继承自
DefaultContractResolver,重写CreateProperties方法过滤需要忽略的成员:
using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; public class IgnoreSpecificBaseMembersResolver : DefaultContractResolver { // 存储需要忽略的基类及其对应的成员名 private readonly Dictionary<Type, HashSet<string>> _ignoredMemberMap; public IgnoreSpecificBaseMembersResolver() { _ignoredMemberMap = new Dictionary<Type, HashSet<string>> { { typeof(SomeBaseClass), new HashSet<string> { nameof(SomeBaseClass.DontSerialize) } }, { typeof(SomeOtherBaseClass), new HashSet<string> { nameof(SomeOtherBaseClass.DontSerialize) } }, { typeof(YetAnotherBaseClass), new HashSet<string> { nameof(YetAnotherBaseClass.DontSerialize) } } }; } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { // 先获取默认的所有可序列化属性 var properties = base.CreateProperties(type, memberSerialization); // 检查当前类型是否是目标基类的子类(或基类本身) foreach (var (baseType, ignoredMembers) in _ignoredMemberMap) { if (baseType.IsAssignableFrom(type)) { // 过滤掉需要忽略的成员 properties = properties.Where(p => !ignoredMembers.Contains(p.PropertyName)).ToList(); break; // 找到匹配的基类后停止,避免重复过滤 } } return properties; } }
- 使用这个Resolver执行序列化:
// 建议缓存Resolver实例,因为每次创建会有性能开销 private static readonly IgnoreSpecificBaseMembersResolver _ignoreResolver = new IgnoreSpecificBaseMembersResolver(); // 构建要序列化的对象图(示例数据) var objectGraph = new ObjectGraph { SomeDerivedCollection = new List<SomeDerivedClass> { new SomeDerivedClass { DontSerialize = 123, SomeOtherDerivedCollection = new List<SomeOtherDerivedClass> { new SomeOtherDerivedClass { DontSerialize = 456 } }, YetAnotherDerivedClass = new YetAnotherDerivedClass { DontSerialize = 789 } } } }; // 配置序列化设置 var serializeSettings = new JsonSerializerSettings { ContractResolver = _ignoreResolver, Formatting = Formatting.Indented // 可选,格式化输出JSON }; // 执行序列化 var jsonResult = JsonConvert.SerializeObject(objectGraph, serializeSettings);
方案优势
- 零侵入:完全不需要修改原类代码,完美适配你无法修改基类/派生类的场景。
- 无递归风险:基于默认序列化逻辑扩展,只是过滤掉不需要的属性,不会触发无限递归。
- 灵活可扩展:如果后续需要忽略其他类的成员,只需在
_ignoredMemberMap中添加新的类型和成员名即可。
备选方案:修复JsonConverter的递归问题
如果你坚持要使用JsonConverter,可以通过在WriteJson中创建不带当前Converter的临时序列化器来避免递归:
using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public class IgnoreDontSerializeConverter : JsonConverter { public override bool CanConvert(Type objectType) { // 匹配所有需要处理的基类及其子类 return typeof(SomeBaseClass).IsAssignableFrom(objectType) || typeof(SomeOtherBaseClass).IsAssignableFrom(objectType) || typeof(YetAnotherBaseClass).IsAssignableFrom(objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 创建临时序列化器,移除当前Converter以避免递归 var tempSerializer = new JsonSerializer(); foreach (var converter in serializer.Converters.Where(c => c.GetType() != GetType())) { tempSerializer.Converters.Add(converter); } // 把对象转为JObject,移除不需要的属性 var jObj = JObject.FromObject(value, tempSerializer); jObj.Remove(nameof(SomeBaseClass.DontSerialize)); // 三个基类的该属性名相同,直接移除 jObj.WriteTo(writer); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 如果需要反序列化,可参考WriteJson逻辑实现,这里示例只处理序列化 throw new NotImplementedException("反序列化逻辑需根据需求实现"); } }
使用时只需将Converter加入序列化设置:
var settings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new IgnoreDontSerializeConverter() }, Formatting = Formatting.Indented }; var json = JsonConvert.SerializeObject(objectGraph, settings);
不过这个方案的性能略低于ContractResolver,因为每次都要额外转换为JObject,适合小范围的序列化场景。
备注:内容来源于stack exchange,提问作者Brian Sweeney
相关产品推荐
相关产品推荐

