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:

  1. because of condition if (property.PropertyType.Assembly == objType.Assembly) you will omit System.Collections like List<>
  2. you do not treat differently propValue that are collections. Hence it will print List 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);
        }
    }
}