将JObject转换为Dictionary<string, object>。这可能吗?

114
我有一个Web API方法,可以接受任意的JSON负载到一个JObject属性中。因此,我不知道会传入什么,但我仍然需要将其转换为.NET类型。我想要一个Dictionary,这样我就可以按照自己的方式处理它。 我已经搜索了很多,但是找不到任何解决方法,最后只能开始一个混乱的方法来逐个转换键和值。有没有简单的方法来做这个?
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))
    )
)

2
两件事情,JObject已经实现了Dictionary<string, JToken>。还有一个问题,你打算如何处理子属性?你的字典中的值会是另一个Dictionary<string, ?>吗? - Rich
是的@Rich,子属性将成为另一个Dictionary<string, object>。 - tucaz
请参阅如何使用JSON.NET将嵌套/递归字典和列表反序列化?。第一个答案中的ToObject(JToken)辅助方法将以最少的代码完成此转换。 - Brian Rogers
5个回答

206

如果您有JObject对象,则以下方法可能适用:

JObject person;
var values = person.ToObject<Dictionary<string, object>>();
如果您没有 JObject,可以使用 Newtonsoft.Json.Linq 扩展方法创建一个。
using Newtonsoft.Json.Linq;

var values = JObject.FromObject(person).ToObject<Dictionary<string, object>>();
否则,这个答案可能会引导你朝正确的方向前进,因为它将JSON字符串反序列化为字典。
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);

+1 是因为如果你有一个基元字典,这个方法会特别好用。例如,下面这行代码对我来说完美地运行了:Dictionary<string, decimal> feeChanges = dict.feeChanges.ToObject<Dictionary<string, decimal>>(); - allen1
2
DeserializeObject<Dictionary<string, object>> 对我非常有用;最终我通过 DeserializeObject<Dictionary<string, object>[]> 将其转换为字典数组以满足我的需求。 - BrainSlugs83

33

由于没有一个答案完全满意,我最终使用了两个答案的混合。

ToObject()可以处理JSON对象的第一层属性,但嵌套对象不会转换为Dictionary()。

使用ToObject()处理第一层属性已经足够了,没有必要手动处理所有内容。

以下是代码:

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

它可能存在一些边缘情况,导致它无法正常工作并且性能不是最强的特点。

谢谢大家!


如果数组的值本身是JSON(即JObject),那该怎么办?您不需要将它们转换为Dictionary<string,object> - Nawaz
@Nawaz,我认为他确实这样做了--这里倒数第二行代码对内部的“JObject”递归调用了该方法。 - BrainSlugs83
1
@BrainSlugs83:是的。它会以递归的方式调用,但是 JArrays 的元素可能是 JOBjectJArrray,这些需要转换为 C# 数组C# 字典,但代码没有实现。 - Nawaz
@BrainSlugs83,我知道你发表这条评论已经有一段时间了,但你是完全正确的。在JArray由JArray组成的情况下,你该如何填充键值对?似乎Dictionary<string,object>数据结构在可能出现两层嵌套且没有属性名称的情况下会出现问题。 - Miek

26

我已经修改了代码,递归 JArrays 和 JObjects 嵌套在 JArrays/JObjects 中,这是被@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;
    }
}

8
这里有一个更简化的版本:
    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;
    }
如果使用C# 7,我们可以使用模式匹配,代码如下:
    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;
    }

3
听起来这是扩展方法的一个很好的应用场景 - 我有一些相关代码,很容易转换成Json.NET格式(感谢NuGet!): ```csharp // 将对象转换为json字符串 public static string ToJson(this object obj) { return JsonConvert.SerializeObject(obj); } ``` 当然,这只是一个快速的解决方案 - 你需要进行清理等操作。
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}
        };
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,