如何用AutoMapper、Json.NET等库基于变更JSON更新父子对象属性
解决方案:用增量JSON更新C#对象
嘿,这个场景我熟!很多系统做增量更新的时候都会遇到这种只传变更属性的情况,我给你分享两种在C#里最实用的实现方式,按需选就行~
首先先补全你没写完的ChildObject定义(方便后续代码示例):
public class ChildObject { public int ChildID { get; set; } public string ChildProp1 { get; set; } public string ChildProp2 { get; set; } }
方法1:手动映射更新(可控性强,适合属性少的场景)
这种方式的核心思路是:把增量JSON反序列化成临时对象,然后逐个判断临时对象的属性是否有值(非null),有值就覆盖原始对象的对应属性。对于集合类的子对象,通过主键(比如ChildID)匹配后再更新。
实现代码
using Newtonsoft.Json; using System.Linq; public class ParentObject { public int ID { get; set; } public string Prop1 { get; set; } public string Prop2 { get; set; } public List<ChildObject> Children { get; set; } = new List<ChildObject>(); } public static void UpdateParentManually(ParentObject original, string updateJson) { // 反序列化增量JSON,配置NullValueHandling.Ignore保证未传的属性不会被设为null var updateTemp = JsonConvert.DeserializeObject<ParentObject>(updateJson, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); // 更新父对象的基础属性(主键ID一般不建议更新,根据业务调整) if (updateTemp.Prop1 != null) original.Prop1 = updateTemp.Prop1; if (updateTemp.Prop2 != null) original.Prop2 = updateTemp.Prop2; // 处理子对象集合:通过ChildID匹配,只更新变更的子项属性 if (updateTemp.Children != null && updateTemp.Children.Any()) { foreach (var updatedChild in updateTemp.Children) { var targetChild = original.Children.FirstOrDefault(c => c.ChildID == updatedChild.ChildID); if (targetChild != null) { if (updatedChild.ChildProp1 != null) targetChild.ChildProp1 = updatedChild.ChildProp1; if (updatedChild.ChildProp2 != null) targetChild.ChildProp2 = updatedChild.ChildProp2; } // 如果业务允许新增子项,这里可以加逻辑:if(targetChild == null) original.Children.Add(updatedChild); } } }
方法2:借助Newtonsoft.Json的Populate方法(高效简洁,适合属性多的场景)
JsonConvert.PopulateObject可以直接把JSON中的属性值填充到已有对象上,省去手动逐个判断的麻烦。但要注意:默认情况下它会直接替换整个集合,所以如果需要保留原始集合中未变更的子项,得单独处理集合部分。
实现代码
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Linq; public static void UpdateParentWithPopulate(ParentObject original, string updateJson) { var updateTemp = JsonConvert.DeserializeObject<ParentObject>(updateJson, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); if (updateTemp.Children != null && updateTemp.Children.Any()) { // 先从JSON中移除Children节点,避免Populate直接替换整个集合 var jUpdate = JObject.Parse(updateJson); jUpdate.Remove("Children"); var filteredJson = jUpdate.ToString(); // 先填充父对象的基础属性 JsonConvert.PopulateObject(filteredJson, original, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); // 单独处理子对象的增量更新,对每个匹配的子项用Populate简化操作 foreach (var updatedChild in updateTemp.Children) { var targetChild = original.Children.FirstOrDefault(c => c.ChildID == updatedChild.ChildID); if (targetChild != null) { JsonConvert.PopulateObject(JsonConvert.SerializeObject(updatedChild), targetChild, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); } // 新增子项逻辑同上,按需添加 } } else { // 没有子对象变更,直接填充整个对象 JsonConvert.PopulateObject(updateJson, original, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); } }
使用示例
// 原始对象 var originalParent = new ParentObject { ID = 1, Prop1 = "Original Prop1", Prop2 = "Original Prop2", Children = new List<ChildObject> { new ChildObject { ChildID = 101, ChildProp1 = "Original Child1 Prop1", ChildProp2 = "Original Child1 Prop2" }, new ChildObject { ChildID = 102, ChildProp1 = "Original Child2 Prop1", ChildProp2 = "Original Child2 Prop2" } } }; // 增量JSON:只修改了Prop1和ChildID=101的ChildProp2 var updateJson = @" { ""Prop1"": ""Updated Prop1"", ""Children"": [ { ""ChildID"": 101, ""ChildProp2"": ""Updated Child1 Prop2"" } ] }"; // 任选一种方法更新 UpdateParentManually(originalParent, updateJson); // UpdateParentWithPopulate(originalParent, updateJson); // 验证结果 Console.WriteLine(originalParent.Prop1); // 输出:Updated Prop1 Console.WriteLine(originalParent.Children[0].ChildProp2); // 输出:Updated Child1 Prop2 Console.WriteLine(originalParent.Children[1].ChildProp1); // 输出:Original Child2 Prop1
关键点总结
- NullValueHandling配置:一定要设置
NullValueHandling.Ignore,避免未传的属性被覆盖为null。 - 集合处理逻辑:如果要保留原始集合中的未变更子项,必须通过主键匹配后单独更新,不能直接替换整个集合。
- 业务适配:根据实际需求决定是否允许新增/删除子项,在代码中添加对应的逻辑即可。
内容的提问来源于stack exchange,提问作者goroth
相关产品推荐
相关产品推荐

