C#:反序列化后合并强类型模型的空值与缺失属性
解决方案:配置自动合并实现
针对新版本部署后现有配置缺失新增属性导致空引用的问题,以下是基于反射的递归合并实现,可保留用户现有配置内容,同时补全默认配置中的新增属性、嵌套对象和数组缺失项。
核心思路
- 递归处理嵌套对象:遍历所有属性,对嵌套类进行深度合并,确保新增的嵌套配置项被补全
- 智能合并数组:针对
Feature这类带标识(Name)的数组,按标识匹配保留现有项,追加默认配置中的新项;普通数组直接合并 - 保留用户配置:仅当现有属性为默认值(null或值类型默认)时,才用默认配置的值替换
- 避免污染原对象:克隆默认配置实例,防止修改原始默认值
完整实现代码
using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using Newtonsoft.Json; using System.IO; private static GuardSettings Merge(GuardSettings defaultSettings, GuardSettings existingSettings) { if (existingSettings == null) return Clone(defaultSettings); foreach (var prop in typeof(GuardSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var defaultValue = prop.GetValue(defaultSettings); var existingValue = prop.GetValue(existingSettings); if (prop.PropertyType.IsArray) { MergeArrays(prop, existingSettings, defaultValue, existingValue); } else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { var mergedValue = MergeNestedObject(defaultValue, existingValue, prop.PropertyType); prop.SetValue(existingSettings, mergedValue); } else { if (IsDefaultValue(existingValue)) { prop.SetValue(existingSettings, defaultValue); } } } return existingSettings; } // 克隆对象,避免修改原默认配置实例 private static T Clone<T>(T obj) where T : class, new() { if (obj == null) return null; using var ms = new MemoryStream(); JsonSerializer.CreateDefault().Serialize(new StreamWriter(ms), obj); ms.Position = 0; return JsonSerializer.CreateDefault().Deserialize<T>(new JsonTextReader(new StreamReader(ms))); } // 判断是否为类型默认值 private static bool IsDefaultValue(object value) { if (value == null) return true; var type = value.GetType(); return type.IsValueType && Activator.CreateInstance(type).Equals(value); } // 合并数组,针对Feature做按Name匹配的智能合并 private static void MergeArrays(PropertyInfo prop, object target, object defaultValue, object existingValue) { if (defaultValue == null) return; var defaultArray = (Array)defaultValue; var existingArray = existingValue as Array ?? Array.CreateInstance(prop.PropertyType.GetElementType(), 0); // 处理Feature数组:按Name匹配,保留现有配置,新增默认项 if (prop.PropertyType == typeof(Feature[])) { var existingFeatures = existingArray.Cast<Feature>().ToList(); foreach (var defaultFeature in defaultArray.Cast<Feature>()) { var existingMatch = existingFeatures.FirstOrDefault(f => f.Name == defaultFeature.Name); if (existingMatch == null) { existingFeatures.Add(Clone(defaultFeature)); } else { // 合并单个Feature的嵌套属性(如Arguments) MergeNestedObject(defaultFeature, existingMatch, typeof(Feature)); } } prop.SetValue(target, existingFeatures.ToArray()); return; } // 普通数组:合并所有元素(可根据需求调整去重逻辑) var mergedArray = Array.CreateInstance(prop.PropertyType.GetElementType(), existingArray.Length + defaultArray.Length); existingArray.CopyTo(mergedArray, 0); defaultArray.CopyTo(mergedArray, existingArray.Length); prop.SetValue(target, mergedArray); } // 递归合并嵌套对象属性 private static object MergeNestedObject(object defaultValue, object existingValue, Type type) { if (existingValue == null) { return defaultValue != null ? Clone(Activator.CreateInstance(type)) : null; } foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var defaultPropValue = prop.GetValue(defaultValue); var existingPropValue = prop.GetValue(existingValue); if (prop.PropertyType.IsArray) { MergeArrays(prop, existingValue, defaultPropValue, existingPropValue); } else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { var mergedPropValue = MergeNestedObject(defaultPropValue, existingPropValue, prop.PropertyType); prop.SetValue(existingValue, mergedPropValue); } else { if (IsDefaultValue(existingPropValue)) { prop.SetValue(existingValue, defaultPropValue); } } } return existingValue; }
关键说明
- 嵌套对象处理:所有自定义类(如
UnturnedGuardOptions)都会被递归合并,无需单独编写每个类的合并逻辑 - 数组合并逻辑:针对
Feature数组做了特殊处理,按Name字段匹配,确保用户已配置的Feature不会被覆盖,同时新增默认配置中的Feature - 序列化克隆:使用Json.NET克隆对象,避免修改原始默认配置实例;若项目使用
System.Text.Json,可替换对应的序列化代码 - 默认值判断:仅当现有属性为null(引用类型)或值类型默认值(如
false、0)时,才用默认配置的值替换,完全保留用户已设置的内容
使用示例
// 加载默认配置和用户现有配置 var defaultSettings = LoadDefaultConfig(); // 从嵌入资源或默认文件加载 var existingSettings = LoadUserConfig(); // 从用户目录加载现有配置 // 合并配置 var mergedSettings = Merge(defaultSettings, existingSettings); // 保存合并后的配置(根据需要序列化为YAML/XML/JSON) SaveConfig(mergedSettings);
内容的提问来源于stack exchange,提问作者sunnamed
相关产品推荐
相关产品推荐

