124

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))
    )
)
3
  • 3
    Two things, JObject already implements Dictionary<string, JToken>. And question, what is your intent to deal with sub properties. Would that value in your Dictionary be another Dictionary<string, ?>? Commented Feb 15, 2013 at 1:23
  • Yes @Rich, sub properties are going to be another Dictionary<string, object> Commented Feb 15, 2013 at 11:39
  • See also How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?. The ToObject(JToken) helper method in the first answer will do this conversion with minimal code. Commented Feb 19, 2016 at 23:44

5 Answers 5

220

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);
Sign up to request clarification or add additional context in comments.

2 Comments

+1 because this works particularly well if you have a dictionary of primitives. For example, this line of code worked perfectly for me: Dictionary<string, decimal> feeChanges = dict.feeChanges.ToObject<Dictionary<string, decimal>>();
The DeserializeObject<Dictionary<string, object>> worked great for me; I ended up converting it to an array of dictionaries for my needs, via DeserializeObject<Dictionary<string, object>[]>.
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!

4 Comments

What if values of the array are JSON (i.e JObject) themselves ? You're not converting them to Dictionary<string,object>
@Nawaz, I think he does -- the second to last line of code here calls the method recursively for inner JObjects.
@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.
@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<string,object> data structure falls flat for potentially two nesting levels without a property name.
30

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;
    }
}

Comments

9

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;
    }

Comments

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}
        };
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.