递归遍历多层嵌套对象并修改祖辈对象的C#实现问题
嵌套对象跨层级属性填充的无副作用递归实现
需求说明
在.NET 6的C#控制台应用中,需要递归遍历任意深度的嵌套Obj对象:
- 遍历到任意层级的对象时,回溯所有已遍历的祖辈对象
- 根据输入JSON定义的规则修改祖辈对象的指定属性:
targetChildren:指定需要获取值的子/孙辈对象名称valsFromChildren:填充为对应目标子对象的valToGive值runtimeValsFromChildren:填充为对应目标子对象生成的运行时随机数
问题现状
现有递归代码存在以下问题:
- 仅能处理直接父子对象的关联,无法支持跨层级的祖辈-孙辈关联(例如
foo的valsFromChildren未包含孙辈quux的giftFromQuux值) - 直接修改原对象,存在副作用
- 递归逻辑错误,仅处理第一个子对象,后续子对象被忽略
RuntimeVal每次调用都新建Random实例,容易生成重复随机数
额外需求
实现无副作用的递归逻辑:
- 不修改原对象,返回独立内存地址的对象副本
- 支持跨层级回溯所有祖辈对象并更新属性
示例数据与代码
输入JSON
{ "name": "foo", "targetChildren": [ "baz", "quux" ], "valsFromChildren": [], "runtimeValsFromChildren": [], "childObjects": [ { "name": "baz", "valToGive": "giftFromBaz", "targetChildren": [ "qux" ], "valsFromChildren": [], "runtimeValsFromChildren": [], "childObjects": [ { "name": "qux", "valToGive": "giftFromQux" }, { "name": "quux", "valToGive": "giftFromQuux" } ] } ] }
现有C#代码
using System.Text.Json; using System.Text.Json.Serialization; using System.Collections; var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}"; var obj = JsonSerializer.Deserialize<Obj>(json); DoRecursion(obj); var newObjJson = JsonSerializer.Serialize(obj); static Obj DoRecursion(Obj obj) { if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0) return obj; foreach (var child in obj.ChildObjects) { var parent = obj; if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name)) { // Give the values to the parent that is targeting them. parent.ValsFromChildren.Add(child.ValToGive); parent.RuntimeValsFromChildren.Add(child.RuntimeVal); } return DoRecursion(child); } return obj; } class Obj { [JsonPropertyName("name")] public string Name { get; set; } [JsonPropertyName("valToGive")] public string ValToGive { get; set; } [JsonPropertyName("targetChildren")] public List<string> TargetChildren { get; set; } [JsonPropertyName("valsFromChildren")] public List<string> ValsFromChildren { get; set; } [JsonPropertyName("runtimeValsFromChildren")] public List<int> RuntimeValsFromChildren { get; set; } [JsonPropertyName("childObjects")] public List<Obj> ChildObjects { get; set; } [JsonIgnore] public int RuntimeVal => new Random().Next(0, 100); }
期望输出JSON
{ "name": "foo", "targetChildren": [ "baz", "quux" ], "valsFromChildren": [ "giftFromBaz", "giftFromQuux" ], "runtimeValsFromChildren": [ 31, 88 ], "childObjects": [ { "name": "baz", "valToGive": "giftFromBaz", "targetChildren": [ "qux" ], "valsFromChildren": [ "giftFromQux" ], "runtimeValsFromChildren": [ 43 ], "childObjects": [ { "name": "qux", "valToGive": "giftFromQux" }, { "name": "quux", "valToGive": "giftFromQuux" } ] } ] }
实际输出JSON
{ "name": "foo", "targetChildren": [ "baz", "quux" ], "valsFromChildren": [ "giftFromBaz" ], "runtimeValsFromChildren": [ 43 ], "childObjects": [ { "name": "baz", "valToGive": "giftFromBaz", "targetChildren": [ "qux" ], "valsFromChildren": [ "giftFromQux" ], "runtimeValsFromChildren": [ 60 ], "childObjects": [ { "name": "qux", "valToGive": "giftFromQux" }, { "name": "quux", "valToGive": "giftFromQuux" } ] } ] }
解决方案代码
using System.Text.Json; using System.Text.Json.Serialization; var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}"; var originalObj = JsonSerializer.Deserialize<Obj>(json); // 使用同一个Random实例避免重复值 var random = new Random(); var newObj = ProcessNestedObjects(originalObj, new List<Obj>(), random); var newObjJson = JsonSerializer.Serialize(newObj, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(newObjJson); // 递归处理嵌套对象,返回副本,无副作用 static Obj ProcessNestedObjects(Obj originalObj, List<Obj> ancestorCopies, Random random) { // 创建当前对象的深拷贝 var currentCopy = new Obj { Name = originalObj.Name, ValToGive = originalObj.ValToGive, TargetChildren = originalObj.TargetChildren?.ToList() ?? new List<string>(), // 初始化空列表,避免null引用 ValsFromChildren = new List<string>(), RuntimeValsFromChildren = new List<int>(), ChildObjects = new List<Obj>() }; // 生成当前对象的RuntimeVal并缓存,避免多次生成不同值 var currentRuntimeVal = random.Next(0, 100); // 回溯所有祖辈副本,检查是否被目标包含,更新属性 foreach (var ancestor in ancestorCopies) { if (ancestor.TargetChildren != null && ancestor.TargetChildren.Contains(currentCopy.Name)) { ancestor.ValsFromChildren.Add(currentCopy.ValToGive); ancestor.RuntimeValsFromChildren.Add(currentRuntimeVal); } } // 处理子对象:创建新的祖辈列表(包含当前副本) var newAncestors = new List<Obj>(ancestorCopies) { currentCopy }; if (originalObj.ChildObjects != null) { foreach (var child in originalObj.ChildObjects) { var childCopy = ProcessNestedObjects(child, newAncestors, random); currentCopy.ChildObjects.Add(childCopy); } } return currentCopy; } class Obj { [JsonPropertyName("name")] public string Name { get; set; } [JsonPropertyName("valToGive")] public string ValToGive { get; set; } [JsonPropertyName("targetChildren")] public List<string> TargetChildren { get; set; } [JsonPropertyName("valsFromChildren")] public List<string> ValsFromChildren { get; set; } [JsonPropertyName("runtimeValsFromChildren")] public List<int> RuntimeValsFromChildren { get; set; } [JsonPropertyName("childObjects")] public List<Obj> ChildObjects { get; set; } }
代码说明
- 无副作用实现:每次递归都创建对象的深拷贝,完全不修改原对象
- 跨层级回溯:递归时传递祖辈对象的副本列表,当前对象处理完成后,遍历所有祖辈检查是否在
targetChildren中,更新对应属性 - 修复递归逻辑:遍历所有子对象并处理,不再提前返回
- 随机数优化:使用同一个
Random实例,避免因短时间多次调用生成重复随机数,同时缓存当前对象的随机值,确保同一对象只生成一次随机数
内容的提问来源于stack exchange,提问作者pragma
相关产品推荐
相关产品推荐

