Перетворити JObject у словник <рядок, об’єкт>. Це можливо?


80

У мене є метод веб-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))
    )
)

Дякую!


2
Дві речі, JObject вже реалізує Dictionary <string, JToken>. І запитайте, який ви намір мати справу з допоміжними властивостями. Чи буде це значення у вашому Словнику іншим словником <рядок,?>?
Багатий

Так @Rich, субвластивості будуть іншим словником <рядок, об'єкт>
tucaz

Див. Також Як використовувати JSON.NET для десеріалізації у вкладеному / рекурсивному словнику та списку? . ToObject(JToken)Допоміжний метод в першому відповіді буде робити це перетворення з мінімальним кодом.
Брайан Роджерс

Відповіді:


143

Якщо у вас є 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

Відмінно DeserializeObject<Dictionary<string, object>>працював для мене; У підсумку я перетворив його на безліч словників для своїх потреб через DeserializeObject<Dictionary<string, object>[]>.
BrainSlugs83,

25

У підсумку я використав поєднання обох відповідей, оскільки ніхто насправді не прибив це.

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

У нього можуть бути крайні випадки, коли це не спрацює, і продуктивність не найвища його якість.

Спасибі, хлопці!


Що робити, якщо значення масиву є JObjectсамими JSON (тобто )? Ви не перетворюєте їх наDictionary<string,object>
Наваз,

@Nawaz, я думаю, що він це робить - другий до останнього рядка коду тут викликає метод рекурсивно для внутрішніх JObjects.
BrainSlugs83,

@ BrainSlugs83: Так. Він називає це рекурсивно, але все ж, елементи JArraysможуть бути JOBjectабо JArrray, то ті повинні бути перетворені в # масив C , і C # словник , який код не робить.
Наваз

@ BrainSlugs83 Я знаю, що це пройшло деякий час після того, як ви опублікували цей коментар, але ви на 100% праві. У сценарії, коли ваш JArray складається з JArray, як би ви заповнили ключ? Здається, структура даних "Словник <рядок, об'єкт>" падає для потенційно двох рівнів вкладеності без імені властивості.
Miek

16

Ось початкова версія: я модифікував код, щоб повторити 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;
    }
}

3

Це звучить як хороший варіант використання методів розширення - у мене щось валялося, і це було досить просто перетворити на Json.NET (дякую NuGet!):

Звичайно, це швидко зламати разом - ви хотіли б це прибрати тощо.

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

2

Ось простіша версія:

    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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.