web-dev-qa-db-fra.com

Convertissez JObject en dictionnaire <chaîne, objet>. C'est possible?

J'ai une méthode API Web qui accepte une charge JSON arbitraire dans une propriété JObject. En tant que tel, je ne sais pas ce qui va arriver, mais je dois tout de même le traduire en types .NET. J'aimerais avoir un Dictionary<string,object> afin de pouvoir le gérer comme je le souhaite.

J'ai beaucoup cherché, mais je n'ai rien trouvé et j'ai commencé à utiliser une méthode compliquée pour effectuer cette conversion clé par clé, valeur par valeur. Y a-t-il un moyen facile de le faire?

Entrée ->

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

Merci!

53
tucaz

J'ai fini par utiliser un mélange des deux réponses car aucune ne l'a vraiment cloué.

ToObject () peut faire le premier niveau de propriétés dans un objet JSON, mais les objets imbriqués ne seront pas convertis en Dictionary ().

Il n'est également pas nécessaire de tout faire manuellement, car ToObject () est très bon avec les propriétés de premier niveau.

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

Il peut y avoir des cas Edge où cela ne fonctionnera pas et où les performances ne sont pas la plus grande qualité.

Merci les gars!

20
tucaz

Si vous avez des objets JObject, ceci pourrait fonctionner:

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

Si vous n'avez pas de JObject, vous pouvez en créer une avec la méthode d'extension Newtonsoft.Json.Linq:

using Newtonsoft.Json.Linq;

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

Sinon, cette réponse pourrait vous indiquer la bonne direction, car elle désérialise une chaîne JSON en dictionnaire.

var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
97
Daniel Pelsmaeker

Voici la version initiale: J'ai modifié le code en recurse JArrays un JObjects imbriqué dans JArrays/JObjects .

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

Cela semble être un bon cas d’utilisation pour les méthodes d’extension - il me restait quelque chose de facile à convertir en Json.NET (Merci NuGet!):

Bien sûr, ceci est rapidement corrigé - vous voudriez le nettoyer, 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}
        };
    }
}
3
JerKimball

Voici une version plus simple:

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

Si vous utilisez C # 7, vous pouvez utiliser une correspondance de modèle qui se présente comme suit:

    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;
    }
1
Esben Skov Pedersen