DhyMik
DhyMik

Reputation: 373

In C#, how to access a dictionary with items of anonymous type wrapped inside of an "object" type

In C#, I am reading a property Config of type IDictionary<string, object>. I am unable to access the information inside the objectelements. It should be a JSON string, however it is wrapped as object.

In VisualStudio's Immediate window, typing Config outputs:

Count = 1
    [0]: {[items, Count = 3]}

Drilling down into items:

var configItemsElement  = Config["items"]

Typing configItemsElement in Immediate window outputs:

Count = 3
    [0]: {[1, {{ value = SeoChoiceInherit, sortOrder = 1 }}]}
    [1]: {[2, {{ value = SeoChoiceYes, sortOrder = 2 }}]}
    [2]: {[3, {{ value = SeoChoiceNo, sortOrder = 3 }}]}

and typing configItemsElement.ToString() returns this type information:

System.Collections.Generic.Dictionary`2[System.String,<>f__AnonymousType7`2[System.String,System.Int32]]

This is as far as I get. I cannot access the elements of configItemsElement. For instance

configItemsElement[1]

results in error CS0021: Cannot apply indexing with [] to an expression of type 'object'

In VisualStudio's "Autos" window, the information for configItemsElementis

configItemsElement
Value: Count = 3
Type: object {System.Collections.Generic.Dictionary<string, <>f__AnonymousType7<string, int>>}

What does this type information mean? It hints at a dictionary, yet applying a key with configItemsElement[1] results in the error above. How can I cast this object to a type I can use? Any cast I try results in errors.

EDIT: I have no access to the code providing the .Config property, but I can see in SQL explorer the data being read into the property. SQL Explorer shows this data:

{"items":[{"id":1,"value":"SeoChoiceInherit"},{"id":2,"value":"SeoChoiceYes"},{"id":3,"value":"SeoChoiceNo"}]}

Making configItemsElement dynamic, and accessing .value, as suggested in symap's answer, like:

dynamic configItemsElement = Config["Items"];
var myString =configItemsElement.value;

results in error:

''object' does not contain a definition for 'value''

which I understand, since configItemsElement should contain a dictionary . But, as mentioned above, accessing dictionary elements doesn`t work either.

Upvotes: 0

Views: 961

Answers (3)

DhyMik
DhyMik

Reputation: 373

I answer my own question since the valuable input I received pointed me to the right direction, yet did not give me a working solution. I am rather new to Stackoverflow, so if there is something I should learn about this, please let me know.

Also, although I found a working solution, I do not understand why the suggestions by @symaps and @shf301 do not work in this case. This is why I am grateful for further input.

The way I finally got it to work is to:

  • declare my own type for the anonymous type
  • cast configItemsElement to IEnumerable
  • iterate over the dictionary inside of configItemsElement
  • get the 'inner item' of each dictionary item using reflection
  • cast the 'inner item' (of anonymous type) to dynamic
  • and use reflection to access the 2 properties of the 'inner item' (value and sortOrder) to add them to a new list

This is my working code:

...

public class InnerItemType
{
    public int sortOrder { get; set;  }
    public string value { get; set; }
}

....

List<InnerItemType> listOptions = new List<InnerItemType>();

var configItemsElement = Config["items"] as IEnumerable;

foreach (var item in configItemsElement)
{
    dynamic innerItem = item.GetType().GetProperties()[1].GetValue(item);
    listOptions.Add(new InnerItemType() { 
        value = innerItem.GetType().GetProperties()[0].GetValue(innerItem), 
        sortOrder = innerItem.GetType().GetProperties()[1].GetValue(innerItem)
    });
}
...

Thanks to @symaps for pointing out that dynamics need to be used. Thanks to @shf301 for explaining that the anonymous type is the cause of the difficulty. This pointed me to the right direction.

However, curiously, the solution with just dynamics (without reflection) does not work as suggested, in my case, and I don't understand why this is.

item.GetType().GetProperties()[1] reveales the name of the innerItem:

{<>f__AnonymousType7`2[System.String,System.Int32] Value}
    ...
    Name: "Value"

but if I try to get to innerItem through a dynamic type using the property name like this:

foreach (dynamic item in configItemsElement)
{
    dynamic innerItem = item.Value;

I get an error "'System.ValueType' does not contain a definition for 'Value'"

Equally, if I try accessing innerItem.value and innerItem.sortOrder directly without reflection like so:

foreach (var item in configItemsElement)
{
    dynamic innerItem = item.GetType().GetProperties()[1].GetValue(item);
    listOptions.Add(new InnerItemType() { 
        value = innerItem.value, 
        sortOrder = innerItem.sortOrder
    });

gives the same error.

Do I misunderstand the works of dynamic? So, although I found a working solution, I would like to fully understand the underlying problem and I am grateful for further explanation. Thank you!

Upvotes: 1

symaps
symaps

Reputation: 62

You can use a dynamic to access the members without type checking.

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

namespace ObjectDictionary
{
    class Program
    {
        static void Main(string[] args)
        {

            IDictionary <string, object>  dict= new Dictionary<String, object>();
            dict.Add("abc", new {value = "blah", sortOrder = 1});

            dynamic a = dict["abc"];
            Console.WriteLine(a.value);
            Console.WriteLine(a.sortOrder);
        }
    }
}

so your

var configItemsElement  = Config["items"];

would become

dynamic configItemsElement = Config["Items"];
var myString =configItemsElement.value;

Looking at your question the data does not appear to be stored as Json. If you need it as json then you would need to reconstruct the json. You could probably use a json Serializer

 string json = JsonConvert.SerializeObject(configItemsElement, Formatting.Indented);
            Console.WriteLine(json);

Upvotes: 1

shf301
shf301

Reputation: 31394

The type System.Collections.Generic.Dictionary<string, <>f__AnonymousType7<string, int>> means that you have a dictionary where the keys are strings and the values are anonymous types. Since anonymous types have no name (from C#'s perspective anyway, as you can see they get a generated name in the compiled code) there's no usable type for you to cast the values to.

Your issue occurred at the point where the configuration was parsed from JSON. Anonymous types should generally only be used inside if a single methods. If they leak out as object references you get the problems that you are having. If possible the code that generated those configuration elements should be fixed to use a named type.

However if that's no possible your only fallback is to cast the values dynamic.

Upvotes: 2

Related Questions