haxor haxor - 3 months ago 26
C# Question

Converting hierarchy of Dictionary<string, object> to nested class

I have a base class with a series of classes as public members. I'm trying to convert a Dictionary to this class.

Any idea how to get past this exception? This code seems to work great when the base class contains primitive types and not types that I've created (or lists of types that I've created).

Exception output:

================================================================================
= Exception Type: System.InvalidCastException
= Exception Dat System.Collections.ListDictionaryInternal
= Inner Exception:
= Exception Message: Object must implement IConvertible.
= Exception Source: mscorlib
= Exception StackTrace: at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType)
at sandbox.Program.DictToObject[T](IDictionary`2 dict) in D:\Code\Misc\Sandbox\sandbox\Program.cs:line 91
at sandbox.Program.Main(String[] args) in D:\Code\Misc\Sandbox\sandbox\Program.cs:line 48
================================================================================


Code:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace sandbox
{
public class BaseClass
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
public SubClass ScSingleton { get; set; }
public List<SubClass> ScList { get; set; }
}

public class SubClass
{
public int AnotherInt { get; set; }
public string AnotherString { get; set; }
}

public partial class Program
{
static void Main(string[] args)
{
try
{
Dictionary<string, object> outer = new Dictionary<string, object>();
outer.Add("SomeInt", 5);
outer.Add("SomeString", "foo");

Dictionary<string, object> scSingle = new Dictionary<string, object>();
scSingle.Add("AnotherInt", 10);
scSingle.Add("AnotherString", "bar");
outer.Add("ScSingleton", scSingle);

List<Dictionary<string, object>> scList = new List<Dictionary<string, object>>();
scList.Add(scSingle);
outer.Add("ScList", scList);

BaseClass b = DictToObject<BaseClass>(outer);
}
catch (Exception e)
{
PrintException(e);
}
finally
{
Console.WriteLine("");
Console.Write("Press ENTER to exit.");
Console.ReadLine();
}

return;
}

public static T DictToObject<T>(IDictionary<string, object> dict) where T : new()
{
T t = new T();
PropertyInfo[] properties = t.GetType().GetProperties();

foreach (KeyValuePair<string, object> curr in dict)
{
if (String.IsNullOrEmpty(curr.Key)) continue;
if (curr.Value == null) continue;

Type valType = null;
Type newType = null;
PropertyInfo currProperty = null;
foreach (PropertyInfo p in properties)
{
if (String.IsNullOrEmpty(p.Name)) continue;

if (String.Compare(p.Name.ToLower(), curr.Key.ToLower()) == 0)
{
valType = t.GetType().GetProperty(p.Name).PropertyType;
newType = Nullable.GetUnderlyingType(valType) ?? valType;
currProperty = p;
break;
}
}

object newVal = Convert.ChangeType(curr.Value, newType);
t.GetType().GetProperty(currProperty.Name).SetValue(t, newVal);
}

return t;
}

static void PrintException(Exception e)
{
Console.WriteLine("================================================================================");
Console.WriteLine(" = Exception Type: " + e.GetType().ToString());
Console.WriteLine(" = Exception Dat " + e.Data);
Console.WriteLine(" = Inner Exception: " + e.InnerException);
Console.WriteLine(" = Exception Message: " + e.Message);
Console.WriteLine(" = Exception Source: " + e.Source);
Console.WriteLine(" = Exception StackTrace: " + e.StackTrace);
Console.WriteLine("================================================================================");
}
}
}

Answer

If you insist on going the path of black reflection magic, you can accomplish what you are looking for using something like the below (ugly and inefficient, could be beautified and optimized):

interface ICollectionBuilder
{
    object Build(IList dictionaries);
}

interface IDictionaryConverter
{
    object Convert(IDictionary<string, object> dict);
}

class DictionaryConerter<T> : IDictionaryConverter where T : new()
{
    public object Convert(IDictionary<string, object> dict)
    {
        return ConvertTyped(dict);
    }

    public T ConvertTyped(IDictionary<string, object> dict)
    {
        T t = new T();
        PropertyInfo[] properties = t.GetType().GetProperties();

        foreach (KeyValuePair<string, object> curr in dict)
        {
            if (String.IsNullOrEmpty(curr.Key)) continue;
            if (curr.Value == null) continue;

            Type valType = null;
            Type newType = null;
            PropertyInfo currProperty = null;
            foreach (PropertyInfo p in properties)
            {
                if (String.IsNullOrEmpty(p.Name)) continue;

                if (String.Compare(p.Name.ToLower(), curr.Key.ToLower()) == 0)
                {
                    valType = t.GetType().GetProperty(p.Name).PropertyType;
                    newType = Nullable.GetUnderlyingType(valType) ?? valType;
                    currProperty = p;
                    break;
                }
            }

            //you don't have to cast the object here, PropertyInfo.SetValue will accept it "as is":
            object newVal = curr.Value;             

            IDictionary<string, object> curDict = curr.Value as IDictionary<string, object>;
            IList curList = curr.Value as IList;
            if (curDict != null && newType.GetConstructor(Type.EmptyTypes) != null)
            {
                newVal = ((IDictionaryConverter)Activator.CreateInstance(typeof(DictionaryConerter<>).MakeGenericType(newType))).Convert(curDict);
            }
            else if (
                curList != null &&
                curList.OfType<IDictionary<string,object>>().Any() &&
                newType.IsGenericType &&
                newType.GetGenericTypeDefinition() == typeof(List<>) &&
                newType.GetGenericArguments()[0].GetConstructor(Type.EmptyTypes) != null)
            {
                newVal = ((ICollectionBuilder)Activator.CreateInstance(typeof(CollectionBuilder<>).MakeGenericType(newType.GetGenericArguments()[0]))).Build(curList);
            }

            t.GetType().GetProperty(currProperty.Name).SetValue(t, newVal);
        }

        return t;
    }
}

class CollectionBuilder<T> : ICollectionBuilder where T : new()
{
    public object Build(IList dictionaries)
    {
        DictionaryConerter<T> dictConverter = new DictionaryConerter<T>();
        List<T> list = dictionaries
            .OfType<IDictionary<string,object>>()
            .Select(dict => dictConverter.ConvertTyped(dict))
            .ToList();

        return list;
    }
}

And the usage:

BaseClass b = new DictionaryConerter<BaseClass>().ConvertTyped(outer);

Of course, you can cache the instances of ICollectionBuilder and IDictionaryConverter in Dictionary<Type, ICollectionBuilder> and Dictionary<Type, IDictionaryConverter>.

Comments