C# object dumper

Date: 2020-10-14
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

public static class ObjectDumper
{
    public static string Dump(object obj, int level = 3) => DictionaryToString(ToDictionary(obj, level));

    private static bool IsObject(object o) => o != null && Type.GetTypeCode(o.GetType()) == TypeCode.Object;

    private static bool IsNumeric(Type type)
    {
        if (type.IsEnum)
            return false;
        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            case TypeCode.Object:
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    return IsNumeric(Nullable.GetUnderlyingType(type));
                }
                return false;
            default:
                return false;
        }
    }

    private static string Repeat(string value, int count) => new StringBuilder(value.Length * count).Insert(0, value, count).ToString();

    private static IDictionary<string, object> ToDictionary(object source, int level = 3)
    {
        if (source == null) return null;
        var dictionary = new Dictionary<string, object>();
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
            AddPropertyToDictionary(property, source, dictionary, level);
        return dictionary;
    }

    private static void AddPropertyToDictionary(PropertyDescriptor property, object source, Dictionary<string, object> dictionary, int level)
    {
        object value = null;
        try
        {
            value = property.GetValue(source);
        }
        catch
        {
            // ignore
        }
        if (value == null)
        {
            dictionary.Add(property.Name, null);
            return;
        }
        if (IsObject(value) && level > 0)
        {
            dictionary.Add(property.Name, ToDictionary(value, level - 1));
            return;
        }
        dictionary.Add(property.Name, value);
    }

    private static string DictionaryToString(IDictionary<string, object> dictionary, int level = 0)
    {
        var sb = new StringBuilder();
        sb.AppendLine("{");
        var keys = dictionary.Keys.ToList();
        keys.Sort();
        var prefix = Repeat("   ", level);
        foreach (var key in keys)
        {
            var value = dictionary[key];
            if (value == null)
            {
                sb.AppendLine($"{prefix}{key}: {value}");
                continue;
            }

            if (value is IDictionary<string, object> d)
            {
                sb.AppendLine($"{prefix}{key}: " + DictionaryToString(d, level + 1));
                continue;
            }

            if (IsNumeric(value.GetType()))
                sb.AppendLine($"{prefix}{key}: {value}");
            else
                sb.AppendLine($"{prefix}{key}: \"{value}\"");
        }
        sb.Append($"{prefix}" + "}");
        return sb.ToString();
    }
}
41420cookie-checkC# object dumper