You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#:反序列化后合并强类型模型的空值与缺失属性

解决方案:配置自动合并实现

针对新版本部署后现有配置缺失新增属性导致空引用的问题,以下是基于反射的递归合并实现,可保留用户现有配置内容,同时补全默认配置中的新增属性、嵌套对象和数组缺失项。

核心思路

  1. 递归处理嵌套对象:遍历所有属性,对嵌套类进行深度合并,确保新增的嵌套配置项被补全
  2. 智能合并数组:针对Feature这类带标识(Name)的数组,按标识匹配保留现有项,追加默认配置中的新项;普通数组直接合并
  3. 保留用户配置:仅当现有属性为默认值(null或值类型默认)时,才用默认配置的值替换
  4. 避免污染原对象:克隆默认配置实例,防止修改原始默认值

完整实现代码

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;
}

关键说明

  1. 嵌套对象处理:所有自定义类(如UnturnedGuardOptions)都会被递归合并,无需单独编写每个类的合并逻辑
  2. 数组合并逻辑:针对Feature数组做了特殊处理,按Name字段匹配,确保用户已配置的Feature不会被覆盖,同时新增默认配置中的Feature
  3. 序列化克隆:使用Json.NET克隆对象,避免修改原始默认配置实例;若项目使用System.Text.Json,可替换对应的序列化代码
  4. 默认值判断:仅当现有属性为null(引用类型)或值类型默认值(如false、0)时,才用默认配置的值替换,完全保留用户已设置的内容

使用示例

// 加载默认配置和用户现有配置
var defaultSettings = LoadDefaultConfig(); // 从嵌入资源或默认文件加载
var existingSettings = LoadUserConfig(); // 从用户目录加载现有配置

// 合并配置
var mergedSettings = Merge(defaultSettings, existingSettings);

// 保存合并后的配置(根据需要序列化为YAML/XML/JSON)
SaveConfig(mergedSettings);

内容的提问来源于stack exchange,提问作者sunnamed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 22:14:53