113

I have a web API method that accepts an arbitrary json payload into a JObject property. As such I don't know what's coming but I still need to translate it to .NET types. I would like to have a Dictionary<string,object> so that I can deal with it any way I want to.

I have searched a lot, but couldn't find anything and ended up starting a messy method to do this conversion, key by key, value by value. Is there an easy way to do it?

Input ->

JObject person = new JObject(
    new JProperty("Name", "John Smith"),
    new JProperty("BirthDate", new DateTime(1983, 3, 20)),
    new JProperty("Hobbies", new JArray("Play football", "Programming")),
    new JProperty("Extra", new JObject(
        new JProperty("Foo", 1),
        new JProperty("Bar", new JArray(1, 2, 3))
    )
)

Thanks!

Nic Foster
  • 2,864
  • 1
  • 27
  • 45
tucaz
  • 6,524
  • 6
  • 37
  • 60
  • 2
    Two things, JObject already implements Dictionary. And question, what is your intent to deal with sub properties. Would that value in your Dictionary be another Dictionary? – Rich Feb 15 '13 at 01:23
  • Yes @Rich, sub properties are going to be another Dictionary – tucaz Feb 15 '13 at 11:39
  • See also [How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?](http://stackoverflow.com/q/5546142/10263). The `ToObject(JToken)` helper method in the first answer will do this conversion with minimal code. – Brian Rogers Feb 19 '16 at 23:44

5 Answers5

204

If you have JObject objects, the following might work:

JObject person;
var values = person.ToObject<Dictionary<string, object>>();

If you do not have a JObject you can create one with the Newtonsoft.Json.Linq extension method:

using Newtonsoft.Json.Linq;

var values = JObject.FromObject(person).ToObject<Dictionary<string, object>>();

Otherwise, this answer might point you in the right direction, as it deserializes a JSON string to a Dictionary.

var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
Steve
  • 25,806
  • 2
  • 33
  • 43
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • +1 because this works particularly well if you have a dictionary of primitives. For example, this line of code worked perfectly for me: Dictionary feeChanges = dict.feeChanges.ToObject>(); – allen1 Jun 08 '15 at 14:40
  • 2
    The `DeserializeObject>` worked great for me; I ended up converting it to an array of dictionaries for my needs, via `DeserializeObject[]>`. – BrainSlugs83 Sep 07 '16 at 20:27
33

I ended up using a mix of both answers as none really nailed it.

ToObject() can do the first level of properties in a JSON object, but nested objects won't be converted to Dictionary().

There's also no need to do everything manually as ToObject() is pretty good with first level properties.

Here is the code:

public static class JObjectExtensions
{
    public static IDictionary<string, object> ToDictionary(this JObject @object)
    {
        var result = @object.ToObject<Dictionary<string, object>>();

        var JObjectKeys = (from r in result
                           let key = r.Key
                           let value = r.Value
                           where value.GetType() == typeof(JObject)
                           select key).ToList();

        var JArrayKeys = (from r in result
                          let key = r.Key
                          let value = r.Value
                          where value.GetType() == typeof(JArray)
                          select key).ToList();

        JArrayKeys.ForEach(key => result[key] = ((JArray)result[key]).Values().Select(x => ((JValue)x).Value).ToArray());
        JObjectKeys.ForEach(key => result[key] = ToDictionary(result[key] as JObject));

        return result;
    }
}

It might have edge cases where it won't work and the performance is not the strongest quality of it.

Thanks guys!

tucaz
  • 6,524
  • 6
  • 37
  • 60
  • What if values of the array are JSON (i.e `JObject`) themselves ? You're not converting them to `Dictionary` – Nawaz Jan 25 '16 at 09:04
  • @Nawaz, I think he does -- the second to last line of code here calls the method recursively for inner `JObject`s. – BrainSlugs83 Sep 07 '16 at 20:26
  • 1
    @BrainSlugs83: Yes. It calls it recursively, but still , elements of `JArrays` could be `JOBject` or `JArrray`, then those need to be converted into *C# array*, and *C# Dictionary*, which the code doesn't do. – Nawaz Sep 08 '16 at 03:12
  • @BrainSlugs83 I know this is a while after you posted this comment, but you're 100% right. In the scenario where your JArray is composed of JArray, how would you fill the key? Seems the Dictionary data structure falls flat for potentially two nesting levels without a property name. – Miek Jan 03 '18 at 23:10
26

I've modified the code to recurse JArrays an JObjects nested in JArrays/JObjects, which the accepted answer does not, as pointed out by @Nawaz.

using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

public static class JsonConversionExtensions
{
    public static IDictionary<string, object> ToDictionary(this JObject json)
    {
        var propertyValuePairs = json.ToObject<Dictionary<string, object>>();
        ProcessJObjectProperties(propertyValuePairs);
        ProcessJArrayProperties(propertyValuePairs);
        return propertyValuePairs;
    }

    private static void ProcessJObjectProperties(IDictionary<string, object> propertyValuePairs)
    {
        var objectPropertyNames = (from property in propertyValuePairs
            let propertyName = property.Key
            let value = property.Value
            where value is JObject
            select propertyName).ToList();

        objectPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToDictionary((JObject) propertyValuePairs[propertyName]));
    }

    private static void ProcessJArrayProperties(IDictionary<string, object> propertyValuePairs)
    {
        var arrayPropertyNames = (from property in propertyValuePairs
            let propertyName = property.Key
            let value = property.Value
            where value is JArray
            select propertyName).ToList();

        arrayPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToArray((JArray) propertyValuePairs[propertyName]));
    }

    public static object[] ToArray(this JArray array)
    {
        return array.ToObject<object[]>().Select(ProcessArrayEntry).ToArray();
    }

    private static object ProcessArrayEntry(object value)
    {
        if (value is JObject)
        {
            return ToDictionary((JObject) value);
        }
        if (value is JArray)
        {
            return ToArray((JArray) value);
        }
        return value;
    }
}
Uli
  • 1,565
  • 14
  • 24
8

Here is a simpler version:

    public static object ToCollections(object o)
    {
        var jo = o as JObject;
        if (jo != null) return jo.ToObject<IDictionary<string, object>>().ToDictionary(k => k.Key, v => ToCollections(v.Value));
        var ja = o as JArray;
        if (ja != null) return ja.ToObject<List<object>>().Select(ToCollections).ToList();
        return o;
    }

If using C# 7 we can use pattern matching where it would look like this:

    public static object ToCollections(object o)
    {
        if (o is JObject jo) return jo.ToObject<IDictionary<string, object>>().ToDictionary(k => k.Key, v => ToCollections(v.Value));
        if (o is JArray ja) return ja.ToObject<List<object>>().Select(ToCollections).ToList();
        return o;
    }
Esben Skov Pedersen
  • 4,437
  • 2
  • 32
  • 46
3

Sounds like a good use case for extension methods - I had something lying around that was pretty straightforward to convert to Json.NET (Thanks NuGet!):

Of course, this is quickly hacked together - you'd want to clean it up, etc.

public static class JTokenExt
{
    public static Dictionary<string, object> 
         Bagify(this JToken obj, string name = null)
    {
        name = name ?? "obj";
        if(obj is JObject)
        {
            var asBag =
                from prop in (obj as JObject).Properties()
                let propName = prop.Name
                let propValue = prop.Value is JValue 
                    ? new Dictionary<string,object>()
                        {
                            {prop.Name, prop.Value}
                        } 
                    :  prop.Value.Bagify(prop.Name)
                select new KeyValuePair<string, object>(propName, propValue);
            return asBag.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }
        if(obj is JArray)
        {
            var vals = (obj as JArray).Values();
            var alldicts = vals
                .SelectMany(val => val.Bagify(name))
                .Select(x => x.Value)
                .ToArray();
            return new Dictionary<string,object>()
            { 
                {name, (object)alldicts}
            };
        }
        if(obj is JValue)
        {
            return new Dictionary<string,object>()
            { 
                {name, (obj as JValue)}
            };
        }
        return new Dictionary<string,object>()
        { 
            {name, null}
        };
    }
}
JerKimball
  • 16,584
  • 3
  • 43
  • 55