Newtonsoft Json Serializer/Deserializer - how to use generic objects instead of JObject?

39,446

JObjects map most directly to Dictionary<string, object>s, since they're each simply a collection of keys to values. If you know that the value is always a string, you can make it a Dictionary<string, string>.

[DataMember(Name = "parameters")]
public List<Dictionary<string, object>> Parameters { get; set; }

// or

[DataMember(Name = "parameters")]
public List<Dictionary<string, string>> Parameters { get; set; }

// e.g.
var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);

foreach (var dict in myNewObject.Parameters)
{
    foreach (var pair in dict)
    {
        Console.WriteLine("\t\tKey is {0}", pair.Key);
        Console.WriteLine("\t\tValue is {0}", pair.Value);
    }
}
Share:
39,446
Stephan G
Author by

Stephan G

Updated on January 10, 2020

Comments

  • Stephan G
    Stephan G over 4 years

    I have JSON specified by a restful service I am consuming that looks like this as an example:

    {
      "id": "97",
      "name": "Tom Production",
      "description": null,
      "parameters": [
        {
          "first_parameter_name": "First Parameter Value"
        },
        {
          "second_parameter_name": "Second Parameter Value"
        }
      ]
    }
    

    Note that the property names id, name, description, and parameters are all established as part of the specification. The collection of generic parameters, shown in my example as "first_parameter_name" and "second_parameter_name" are not specified.... could be anything and I want to map them to generically typed objects.

    I have declared an object for this as:

    [DataContract (Name = "MyClass")]
    public class MyClass
    {
        [DataMember (Name = "id")]
        public string Id { get; set; }
    
        [DataMember(Name = "name")]
        public string Name { get; set; }
    
        [DataMember(Name = "description")]
        public string Description { get; set; }
    
        [DataMember(Name = "parameters")]
        public List<object> Parameters { get; set; }
    }
    

    Serialization works fine, exactly as I expect:

            var myObject = new MyClass();
            myObject.Id = "97";
            myObject.Name = "Tom Production";
            myObject.Parameters = new List<object>();
            myObject.Parameters.Add(new { first_parameter_name = "First Parameter Value" });
            myObject.Parameters.Add(new { second_parameter_name = "Second Parameter Value" });
            string json = JsonConvert.SerializeObject(myObject);
            Console.WriteLine(json);
    

    yields the JSON I am looking for, exactly as at the top of this posting.

    HOWEVER.

    Deserialization does NOT work fine. If it worked the way I hope it to, which would be to create generic types just like I had created, and the following code should work.... but instead it throws a reflection exception:

            var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);
    
            foreach (object o in myNewObject.Parameters)
            {
                Type t = o.GetType();
                Console.WriteLine("\tType is {0}", t);
                foreach (PropertyInfo pi in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
                {
                    Console.WriteLine("\t\tName is {0}", pi.Name);
                    Console.WriteLine("\t\tValue is {0}", pi.GetValue(o, null));
                }
            }
    

    Instead, I have to write code that is Newtonsoft-specific (ick) to use a kind of fake Newtonsoft reflection:

            var myNewObject = JsonConvert.DeserializeObject<MyClass>(json);
    
            foreach (object o in myNewObject.Parameters)
            {
                var jo = o as JObject;
                if (jo != null)
                {
                    foreach (JProperty prop in jo.Properties())
                    {
                        Console.WriteLine("\t\tName is {0}", prop.Name);
                        Console.WriteLine("\t\tValue is {0}", prop.Value);
                    }
                }
            }
    

    Is there a way that I can control the Deserializer so that it will generate the proper generic types rather than the JObject type with the JProperties?

    Many thanks in advance.

    • Brian Rogers
      Brian Rogers over 9 years
      Anonymous types are compile-time classes. You can create them in your code because the compiler sets up concrete classes for you behind the scenes. When you deserialize, it is not possible to re-create the anonymous classes because the types are dependent on the JSON, which is only known at runtime. That is why Json.Net cannot recreate the anonymous types. The solution is to use a List<Dictionary<string, string>> like @Tim S. suggested.
    • Servy
      Servy over 9 years
      If you've found a solution to your problem then post it as an answer to the question, not as an edit to the question itself. If the existing answer is sufficient, then there's no need to even do that; just leave it as it sits.
  • Stephan G
    Stephan G over 9 years
    This is perhaps a viable solution.... curious to see if anyone has thoughts about how to get it the way "I really want it", but this could work. Only problem is that Dictionaries of course don't allow duplicate keys, which the specification could allow in theory. Although now that I am looking at your code more carefully, I see that it is a list of dictionaries, so that could be fine. It would be nice if NameValueCollection would work.... but I suspect it won't.... I may try.
  • Tim S.
    Tim S. over 9 years
    JSON doesn't allow duplicate keys, either, so if they do include duplicates within one object, they are giving you invalid input. I'm sure it wouldn't work out-of-the-box, but I'd fully expect that with a custom converter you can replace the List<Dictionary<..>> with a single NameValueCollection, if that sounds like a better option for your data specification.