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
相关产品推荐
相关产品推荐

