C#提取无限嵌套未知属性对象叶子值类型的最优方案咨询
C#提取无限嵌套对象的叶子值(保留重复属性名)
方案一:反射+递归实现
这种方式直接通过C#反射机制遍历对象结构,适合处理普通.NET对象(包括匿名类型、自定义类),核心是递归判断每个成员是否为目标叶子类型,若是则记录属性名和值,否则继续遍历。
需要注意处理可空值类型(如int?、decimal?)和集合类型(如List<T>、数组),避免遗漏嵌套在集合中的元素。
using System; using System.Collections; using System.Collections.Generic; using System.Reflection; public static class ObjectLeafExtractor { // 定义目标叶子类型 private static readonly HashSet<Type> _targetLeafTypes = new HashSet<Type> { typeof(string), typeof(int), typeof(int?), typeof(double), typeof(double?), typeof(decimal), typeof(decimal?) }; public static IEnumerable<(string PropertyName, object Value)> ExtractLeafValues(object obj, string parentPropertyName = "") { if (obj == null) yield break; var objType = obj.GetType(); // 处理集合类型(数组、List等) if (obj is IEnumerable enumerable && !(obj is string)) { foreach (var item in enumerable) { foreach (var leaf in ExtractLeafValues(item, parentPropertyName)) { yield return leaf; } } yield break; } // 处理叶子类型 var underlyingType = Nullable.GetUnderlyingType(objType) ?? objType; if (_targetLeafTypes.Contains(underlyingType)) { yield return (parentPropertyName, obj); yield break; } // 处理复杂对象的属性 foreach (PropertyInfo prop in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var propValue = prop.GetValue(obj); var currentPropName = string.IsNullOrEmpty(parentPropertyName) ? prop.Name : $"{parentPropertyName}.{prop.Name}"; foreach (var leaf in ExtractLeafValues(propValue, currentPropName)) { yield return leaf; } } } }
使用示例:
// 测试嵌套对象 var testObj = new { Name = "Andrew", Age = 30, Contact = new { Phone = "123456", Address = new { ZipCode = 100000, City = "Beijing" } }, Scores = new List<int> { 90, 85, 95 }, Salary = 12345.67m }; foreach (var leaf in ObjectLeafExtractor.ExtractLeafValues(testObj)) { Console.WriteLine($"{leaf.PropertyName}: {leaf.Value}"); }
方案二:利用Json.NET(Newtonsoft.Json)实现
如果对象结构非常复杂(比如包含ExpandoObject、dynamic类型),用Json.NET处理会更省心,它能自动解析所有嵌套结构,包括动态对象和集合。
核心思路是将对象序列化为JToken,然后递归遍历每个JProperty和JValue,筛选出目标类型的值并保留属性名。
using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; public static class JsonLeafExtractor { public static IEnumerable<(string PropertyName, object Value)> ExtractLeafValues(object obj) { var jToken = JToken.FromObject(obj); return TraverseJToken(jToken); } private static IEnumerable<(string PropertyName, object Value)> TraverseJToken(JToken token, string parentPath = "") { switch (token.Type) { case JTokenType.Object: foreach (JProperty prop in token.Children<JProperty>()) { var currentPath = string.IsNullOrEmpty(parentPath) ? prop.Name : $"{parentPath}.{prop.Name}"; foreach (var leaf in TraverseJToken(prop.Value, currentPath)) { yield return leaf; } } break; case JTokenType.Array: foreach (var item in token.Children()) { foreach (var leaf in TraverseJToken(item, parentPath)) { yield return leaf; } } break; case JTokenType.String: case JTokenType.Integer: case JTokenType.Float: case JTokenType.Decimal: yield return (parentPath, token.Value<object>()); break; // 忽略其他类型(如null、布尔值等) default: yield break; } } }
使用示例:
// 测试动态对象 dynamic dynamicObj = new System.Dynamic.ExpandoObject(); dynamicObj.Name = "Gray"; dynamicObj.Info = new ExpandoObject(); dynamicObj.Info.Height = 175.5; dynamicObj.Scores = new[] { 88, 92 }; foreach (var leaf in JsonLeafExtractor.ExtractLeafValues(dynamicObj)) { Console.WriteLine($"{leaf.PropertyName}: {leaf.Value}"); }
方案对比
- 反射方案:无需第三方库,适合处理强类型对象,但对动态对象(ExpandoObject)的支持有限。
- Json.NET方案:对动态对象、匿名类型、复杂嵌套结构兼容性更好,但需要引入Newtonsoft.Json NuGet包。
两种方案都会保留重复的属性名(比如嵌套结构中不同层级出现同名属性时,会分别记录完整路径和值)。
内容的提问来源于stack exchange,提问作者Andrew Gray
相关产品推荐
相关产品推荐

