将 JObject 转换成 Dictionary < string,object > . 这可能吗?

我有一个 web API 方法,它接受任意的 json 有效负载到 JObject属性中。因此我不知道接下来会发生什么,但是我仍然需要把它翻译成。NET 类型。我想有一个 Dictionary<string,object>,以便我可以处理它的任何方式,我想要的。

我搜索了很多,但是什么也没有找到,最后开始了一个混乱的方法来进行这种转换,一个键一个键,一个值一个值的转换。有什么简单的方法吗?

输入->

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))
)
)

谢谢!

120413 次浏览

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

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

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!

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

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