Search code examples
c#linqdictionarydynamic-properties

How would I convert List<Dictionary<string, object>> into a List<[new class with dynamic properties]>


I have a design where class holds a List<> of Summary objects, and each Summary is a dictionary of semi-dynamic properties. That is, every Summary in a given list will have the same keys in the dictionary. I'm using this design to build a set of dynamic "properties" to keep track of summary values since, per my project specs, these summary values will be configurable at runtime.

The question is: How can I flatten this List so that each item in the list is treated as-if the keys in the dictionary were actual properties?

I've tried different variations on converting the Dictionary to a list, but that seems inherently wrong since I really need to treat the keys as properties. I'm guessing I need to use the new dynamic feature of C# 4.0+ and the ExpandoObject, but I can't quite get it right.

The code below shows the basic setup, with "flattenedSummary" giving me what I want - a new list of a dynamic type whose properties are the keys of the Summary's dictionary. However, that is flawed in that I've hard-coded the property names and I can't do that since I won't really know them until runtime.

The flattenedSummary2 version attempts to flatten the list but falls short as the returned type is still a List and not the List that I want.

    public class Summary : Dictionary<string, object>
    {
    }

    public class ClassA
    {
        public List<Summary> Summaries = new List<Summary>();
    }

    static void Main(string[] args)
    {
        ClassA a = new ClassA();
        var summary = new Summary();
        summary.Add("Year", 2010);
        summary.Add("Income", 1000m);
        summary.Add("Expenses", 500m);
        a.Summaries.Add(summary);

        summary = new Summary();
        summary.Add("Year", 2011);
        summary.Add("Income", 2000m);
        summary.Add("Expenses", 700m);
        a.Summaries.Add(summary);

        summary = new Summary();
        summary.Add("Year", 2012);
        summary.Add("Income", 1000m);
        summary.Add("Expenses", 800m);
        a.Summaries.Add(summary);

        var flattenedSummary = from s in a.Summaries select new { Year = s["Year"], Income = s["Income"], Expenses = s["Expenses"] };
        ObjectDumper.Write(flattenedSummary, 1);

        var flattenedSummary2 = Convert(a);
        ObjectDumper.Write(flattenedSummary2, 1);

        Console.ReadKey();
    }

    public static List<ExpandoObject> Convert(ClassA a)
    {
        var list = new List<ExpandoObject>();
        foreach (Summary summary in a.Summaries)
        {
            IDictionary<string, object> fields = new ExpandoObject();
            foreach (var field in summary)
            {
                fields.Add(field.Key.ToString(), field.Value);
            }
            dynamic s = fields;
            list.Add(s);
        }

        return list;
    }

Solution

  • Daniel,

    I found this article that has a solution that might work for you. http://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/

    So you would create the extension method...

      public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary)
            {
                var expando = new ExpandoObject();
                var expandoDic = (IDictionary<string, object>)expando;
    
                // go through the items in the dictionary and copy over the key value pairs)
                foreach (var kvp in dictionary)
                {
                    // if the value can also be turned into an ExpandoObject, then do it!
                    if (kvp.Value is IDictionary<string, object>)
                    {
                        var expandoValue = ((IDictionary<string, object>)kvp.Value).ToExpando();
                        expandoDic.Add(kvp.Key, expandoValue);
                    }
                    else if (kvp.Value is ICollection)
                    {
                        // iterate through the collection and convert any strin-object dictionaries
                        // along the way into expando objects
                        var itemList = new List<object>();
                        foreach (var item in (ICollection)kvp.Value)
                        {
                            if (item is IDictionary<string, object>)
                            {
                                var expandoItem = ((IDictionary<string, object>)item).ToExpando();
                                itemList.Add(expandoItem);
                            }
                            else
                            {
                                itemList.Add(item);
                            }
                        }
    
                        expandoDic.Add(kvp.Key, itemList);
                    }
                    else
                    {
                        expandoDic.Add(kvp);
                    }
                }
    
                return expando;
            }
    

    Then from your Main function...

    List<ExpandoObject> flattenSummary3 = new List<ExpandoObject>();
    foreach ( var s in a.Summaries)
    {
        flattenSummary3.Add(s.ToExpando());
    }
    

    And now the flattenSummary3 variable will contain a List of ExpandObjects that you can refer by properties.

    I hope this helps.