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

Json.NET PopulateObject方法失效问题排查:继承DynamicObject并实现IDictionary<string,object>的自定义类型适配

问题分析与解决方案

我帮你分析下这个问题的核心原因:

你用自定义契约解析器把ElasticObject及其子类映射为DynamicObjectContract,这个契约在反序列化创建新对象(也就是DeserializeObject<T>)时工作正常,因为Json.NET会通过DynamicObject的TrySetMember方法来设置动态属性,同时你的契约解析器也处理了声明属性的序列化。但当调用PopulateObject填充已有对象时,Json.NET的内部填充逻辑并不兼容DynamicObjectContract——尤其是你的类型还同时实现了IDictionary<string, object>接口,这导致了框架内部对对象类型的判断冲突,最终抛出了无法填充JSON对象的错误。

下面给你两个可行的解决方案:

方案一:修改契约解析器,用字典契约兼容填充操作

既然你的ElasticObject已经实现了IDictionary<string, object>,我们可以让契约解析器返回JsonDictionaryContract,同时把子类中声明的属性也纳入契约的属性集合,这样既能处理动态字典属性,也能正确填充声明的属性。

修改你的DefaultJsonContractResolver代码:

public class DefaultJsonContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (typeof(ElasticObject).IsAssignableFrom(objectType))
        {
            // 替换为创建字典契约,而非动态契约
            var contract = CreateDictionaryContract(objectType);
            return contract;
        }
        return base.CreateContract(objectType);
    }

    protected override IList<JsonProperty> CreateProperties(Type objectType, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(objectType, memberSerialization);
        Type elasticObjectType = typeof(ElasticObject);
        
        if ((memberSerialization == MemberSerialization.OptOut) && objectType.IsSubclassOf(elasticObjectType))
        {
            foreach (JsonProperty property in properties)
            {
                if (!property.Ignored && property.DeclaringType.IsSubclassOf(elasticObjectType))
                {
                    // 标记声明属性为有效成员,并使用反射访问器直接操作对象属性
                    property.HasMemberAttribute = true;
                    property.ValueProvider = new ReflectionValueProvider(property.Member);
                }
            }
        }
        return properties;
    }
}

方案二:自定义JsonConverter完全控制填充逻辑

如果你不想修改契约解析器,可以创建一个专门的转换器,手动控制声明属性和动态属性的填充/序列化逻辑:

public class ElasticObjectConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(ElasticObject).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // 复用已有对象或创建新对象
        ElasticObject elasticObj = existingValue as ElasticObject ?? (ElasticObject)Activator.CreateInstance(objectType);
        
        // 先填充声明的属性
        JObject jObj = JObject.Load(reader);
        serializer.Populate(jObj.CreateReader(), elasticObj);
        
        // 再处理动态属性(字典键值对)
        foreach (var prop in jObj.Properties())
        {
            // 跳过已处理的声明属性
            var propertyInfo = objectType.GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (propertyInfo == null)
            {
                elasticObj[prop.Name] = prop.Value.ToObject<object>(serializer);
            }
        }
        
        return elasticObj;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ElasticObject elasticObj = (ElasticObject)value;
        JObject jObj = new JObject();
        
        // 先写入声明的属性
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
        foreach (JsonProperty prop in contract.Properties)
        {
            if (prop.Readable && !prop.Ignored)
            {
                object propValue = prop.ValueProvider.GetValue(value);
                jObj[prop.PropertyName] = propValue != null ? JToken.FromObject(propValue, serializer) : null;
            }
        }
        
        // 再写入动态属性
        foreach (var kvp in elasticObj)
        {
            if (!jObj.ContainsKey(kvp.Key))
            {
                jObj[kvp.Key] = kvp.Value != null ? JToken.FromObject(kvp.Value, serializer) : null;
            }
        }
        
        jObj.WriteTo(writer);
    }
}

然后在序列化配置中注册这个转换器:

JsonSerializerSettings jsonSettings = new JsonSerializerSettings()
{
    CheckAdditionalContent = false,
    DefaultValueHandling = DefaultValueHandling.Populate,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    NullValueHandling = NullValueHandling.Ignore,
    ObjectCreationHandling = ObjectCreationHandling.Auto,
    Converters = new List<JsonConverter> { new ElasticObjectConverter() }
};

验证效果

修改后,无论是DeserializeObject还是PopulateObject都能正常工作:

  • 声明的属性(比如Settings.Name)会被正确赋值
  • 动态属性(比如$schema)会被添加到ElasticObject的字典集合中

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:50:01