Konvertieren Sie JObject in Dictionary <string, object>. Ist es möglich?

77

Ich habe eine Web-API-Methode, die eine beliebige JSON-Nutzlast in eine JObjectEigenschaft akzeptiert . Als solches weiß ich nicht, was kommt, aber ich muss es trotzdem in .NET-Typen übersetzen. Ich hätte gerne eine, Dictionary<string,object>damit ich damit umgehen kann, wie ich will.

Ich habe viel gesucht, aber nichts gefunden und am Ende eine unordentliche Methode gestartet, um diese Konvertierung durchzuführen, Schlüssel für Schlüssel, Wert für Wert. Gibt es eine einfache Möglichkeit, dies zu tun?

Eingabe ->

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

Vielen Dank!

Tucaz
quelle
2
Zwei Dinge, JObject implementiert bereits Dictionary <string, JToken>. Und fragen Sie, was ist Ihre Absicht, mit Untereigenschaften umzugehen. Wäre dieser Wert in Ihrem Wörterbuch ein anderes Wörterbuch <string ,?>?
Rich
Ja @Rich, Sub-Eigenschaften werden ein weiteres Wörterbuch <Zeichenfolge, Objekt> sein
Tucaz
Siehe auch Wie verwende ich JSON.NET zum Deserialisieren in verschachtelte / rekursive Wörterbücher und Listen? . Die ToObject(JToken)Hilfsmethode in der ersten Antwort führt diese Konvertierung mit minimalem Code durch.
Brian Rogers

Antworten:

139

Wenn Sie JObjectObjekte haben, funktioniert möglicherweise Folgendes:

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

Wenn Sie keine haben JObject, können Sie eine mit der Newtonsoft.Json.LinqErweiterungsmethode erstellen :

using Newtonsoft.Json.Linq;

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

Andernfalls weist diese Antwort möglicherweise in die richtige Richtung, da eine JSON-Zeichenfolge in ein Wörterbuch deserialisiert wird.

var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
Daniel AA Pelsmaeker
quelle
+1, da dies besonders gut funktioniert, wenn Sie ein Wörterbuch mit Grundelementen haben. Zum Beispiel funktionierte diese Codezeile perfekt für mich: Dictionary <string, decimal> feeChanges = dict.feeChanges.ToObject <Dictionary <string, decimal >> ();
Allen1
Das DeserializeObject<Dictionary<string, object>>hat super für mich funktioniert; Am Ende habe ich es über in eine Reihe von Wörterbüchern für meine Bedürfnisse konvertiert DeserializeObject<Dictionary<string, object>[]>.
BrainSlugs83
25

Am Ende habe ich eine Mischung aus beiden Antworten verwendet, da es keiner wirklich verstanden hat.

ToObject () kann die erste Eigenschaftsebene in einem JSON-Objekt ausführen, verschachtelte Objekte werden jedoch nicht in Dictionary () konvertiert.

Es ist auch nicht erforderlich, alles manuell zu erledigen, da ToObject () mit Eigenschaften der ersten Ebene ziemlich gut ist.

Hier ist der 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;
    }
}

Es kann Randfälle geben, in denen es nicht funktioniert und die Leistung nicht die stärkste Qualität ist.

Danke Leute!

Tucaz
quelle
Was ist, wenn die Werte des Arrays JSON (dh JObject) selbst sind? Sie konvertieren sie nicht zuDictionary<string,object>
Nawaz
@Nawaz, ich denke er tut es - die vorletzte Codezeile hier ruft die Methode rekursiv für innere JObjects auf.
BrainSlugs83
@ BrainSlugs83: Ja. Es ruft es rekursiv auf, aber dennoch JArrayskönnten Elemente von sein JOBjectoder JArrray, dann müssen diese in C # -Array und C # -Dictionary konvertiert werden , was der Code nicht tut.
Nawaz
@ BrainSlugs83 Ich weiß, dass dies eine Weile dauert, nachdem Sie diesen Kommentar gepostet haben, aber Sie haben 100% Recht. Wie würden Sie in dem Szenario, in dem Ihr JArray aus JArray besteht, den Schlüssel füllen? Die Datenstruktur des Wörterbuchs <Zeichenfolge, Objekt> scheint für möglicherweise zwei Verschachtelungsebenen ohne Eigenschaftsnamen flach zu sein.
Miek
16

Hier ist die Anfangsversion : Ich habe den Code so geändert, dass JArrays und in JArrays / JObjects verschachtelte JObjects wieder verwendet werden , was in der akzeptierten Antwort nicht der Fall ist, wie von @Nawaz hervorgehoben.

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
quelle
3

Klingt nach einem guten Anwendungsfall für Erweiterungsmethoden - ich hatte etwas herumliegen, das ziemlich einfach in Json.NET zu konvertieren war (Danke NuGet!):

Natürlich wird dies schnell zusammen gehackt - Sie möchten es aufräumen usw.

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
quelle
2

Hier ist eine einfachere 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;
    }

Wenn Sie C # 7 verwenden, können wir Pattern Matching verwenden, wo es so aussehen würde:

    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
quelle