Recursively Get Properties & Child Properties Of A Class
Based on Konrad Kokosa's answer:
private string ObjectToString(object obj, int indent = 0)
{
if (obj is null)
{
return "";
}
var sb = new StringBuilder();
string indentString = new string(' ', indent);
Type objType = obj.GetType();
foreach (PropertyInfo property in objType.GetProperties())
{
object propValue = property.GetValue(obj);
var elems = propValue as IList;
if (elems != null)
{
foreach (var item in elems)
{
sb.Append($"{indentString}- {property.Name}\n");
sb.Append(ObjectToString(item, indent + 4));
}
}
else if (property.Name != "ExtensionData")
{
sb.Append($"{indentString}- {property.Name}={propValue}\n");
if (property.PropertyType.Assembly == objType.Assembly)
{
sb.Append(ObjectToString(propValue, indent + 4));
}
}
}
return sb.ToString();
}
UPDATE
Edit the code based on this older question: TargetParameterCountException when enumerating through properties of string
private string ObjectToString(object obj, int indent = 0)
{
var sb = new StringBuilder();
if (obj != null)
{
string indentString = new string(' ', indent);
if (obj is string)
{
sb.Append($"{indentString}- {obj}\n");
}
else if (obj is Array)
{
var elems = obj as IList;
sb.Append($"{indentString}- [{elems.Count}] :\n");
for (int i = 0; i < elems.Count; i++)
{
sb.Append(ObjectToString(elems[i], indent + 4));
}
}
else
{
Type objType = obj.GetType();
PropertyInfo[] props = objType.GetProperties();
foreach (PropertyInfo prop in props)
{
if (prop.GetIndexParameters().Length == 0)
{
object propValue = prop.GetValue(obj);
var elems = propValue as IList;
if (elems != null)
{
foreach (var item in elems)
{
sb.Append($"{indentString}- {prop.Name} :\n");
sb.Append(ObjectToString(item, indent + 4));
}
}
else if (prop.Name != "ExtensionData")
{
sb.Append($"{indentString}- {prop.Name} = {propValue}\n");
if (prop.PropertyType.Assembly == objType.Assembly)
{
sb.Append(ObjectToString(propValue, indent + 4));
}
}
}
else
{
sb.Append($"{indentString}- {prop.Name} ({prop.PropertyType.Name}): <Indexed>\n");
}
}
}
}
return sb.ToString();
}
UPDATE 2
public static string ObjectToString(object obj, int indent = 0)
{
var sb = new StringBuilder();
if (obj != null)
{
string indentString = new string(' ', indent);
if (obj is string || obj.IsNumber())
{
sb.Append($"{indentString}- {obj}\n");
}
else if (obj.GetType().BaseType == typeof(Enum))
{
sb.Append($"{indentString}- {obj.ToString()}\n");
}
else if (obj is Array)
{
var elems = obj as IList;
sb.Append($"{indentString}- [{elems.Count}] :\n");
for (int i = 0; i < elems.Count; i++)
{
sb.Append(ObjectToString(elems[i], indent + 4));
}
}
else
{
Type objType = obj.GetType();
PropertyInfo[] props = objType.GetProperties();
foreach (PropertyInfo prop in props)
{
if (prop.GetIndexParameters().Length == 0)
{
object propValue = prop.GetValue(obj);
var elems = propValue as IList;
if (elems != null)
{
foreach (var item in elems)
{
sb.Append($"{indentString}- {prop.Name} :\n");
sb.Append(ObjectToString(item, indent + 4));
}
}
else if (prop.Name != "ExtensionData")
{
sb.Append($"{indentString}- {prop.Name} = {propValue}\n");
if (prop.PropertyType.Assembly == objType.Assembly)
{
sb.Append(ObjectToString(propValue, indent + 4));
}
}
}
else if (objType.GetProperty("Item") != null)
{
int count = -1;
if (objType.GetProperty("Count") != null &&
objType.GetProperty("Count").PropertyType == typeof(int))
{
count = (int)objType.GetProperty("Count").GetValue(obj, null);
}
for (int i = 0; i < count; i++)
{
object val = prop.GetValue(obj, new object[] { i });
sb.Append(ObjectToString(val, indent + 4));
}
}
}
}
}
return sb.ToString();
}
public static bool IsNumber(this object value)
{
return value is sbyte
|| value is byte
|| value is short
|| value is ushort
|| value is int
|| value is uint
|| value is long
|| value is ulong
|| value is float
|| value is double
|| value is decimal;
}
You have two problems with your code:
- because of condition
if (property.PropertyType.Assembly == objType.Assembly)
you will omitSystem.Collections
likeList<>
- you do not treat differently
propValue
that are collections. Hence it will printList
properties, not its elements properties.
You can change that for example into:
public void PrintProperties(object obj, int indent)
{
if (obj == null) return;
string indentString = new string(' ', indent);
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
foreach (PropertyInfo property in properties)
{
object propValue = property.GetValue(obj, null);
var elems = propValue as IList;
if (elems != null)
{
foreach (var item in elems)
{
PrintProperties(item, indent + 3);
}
}
else
{
// This will not cut-off System.Collections because of the first check
if (property.PropertyType.Assembly == objType.Assembly)
{
Console.WriteLine("{0}{1}:", indentString, property.Name);
PrintProperties(propValue, indent + 2);
}
else
{
Console.WriteLine("{0}{1}: {2}", indentString, property.Name, propValue);
}
}
}
}
You want to handle primitive types and strings separately, and loop over enumerables instead of just taking their ToString() value. So your code could be updated to:
public void PrintProperties(object obj, int indent)
{
if (obj == null) return;
string indentString = new string(' ', indent);
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
foreach (PropertyInfo property in properties)
{
object propValue = property.GetValue(obj, null);
if(property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
Console.WriteLine("{0}{1}: {2}", indentString, property.Name, propValue);
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
Console.WriteLine("{0}{1}:", indentString, property.Name);
IEnumerable enumerable = (IEnumerable)propValue;
foreach(object child in enumerable)
PrintProperties(child, indent + 2);
}
else
{
Console.WriteLine("{0}{1}:", indentString, property.Name);
PrintProperties(propValue, indent + 2);
}
}
}