web-dev-qa-db-fra.com

Comment aplatir un ExpandoObject retourné via JsonResult dans asp.net mvc?

J'aime vraiment le ExpandoObject lors de la compilation d'un objet dynamique côté serveur lors de l'exécution, mais j'ai du mal à aplatir cette chose pendant la sérialisation JSON. Tout d'abord, j'instancie l'objet:

dynamic expando = new ExpandoObject();
var d = expando as IDictionary<string, object>;
expando.Add("SomeProp", SomeValueOrClass);

Jusqu'ici tout va bien. Dans mon contrôleur MVC, je veux ensuite envoyer cela en tant que JsonResult, donc je fais ceci:

return new JsonResult(expando);

Cela sérialise le JSON dans ce qui suit, à consommer par le navigateur:

[{"Key":"SomeProp", "Value": SomeValueOrClass}]

MAIS, ce que j'aimerais vraiment, c'est voir ceci:

{SomeProp: SomeValueOrClass}

Je sais que je peux y parvenir si j'utilise dynamic au lieu de ExpandoObject - JsonResult est capable de sérialiser les propriétés et les valeurs dynamic en un seul objet ( sans activité clé ou valeur), mais la raison pour laquelle j'ai besoin d'utiliser ExpandoObject est parce que je ne connais pas toutes les propriétés que je veux sur l'objet jusqu'à l'exécution, et pour autant que je sache, je ne peux pas ajouter dynamiquement une propriété à un dynamic sans utiliser un ExpandoObject.

Je devrai peut-être passer au crible les activités "Clé", "Valeur" dans mon javascript, mais j'espérais le comprendre avant de l'envoyer au client. Merci de votre aide!

90
TimDog

Vous pouvez également créer un JSONConverter spécial qui ne fonctionne que pour ExpandoObject, puis l'enregistrer dans une instance de JavaScriptSerializer. De cette façon, vous pouvez sérialiser des tableaux d'expando, des combinaisons d'objets expando et ... jusqu'à ce que vous trouviez un autre type d'objet qui ne soit pas sérialisé correctement ("comme vous le souhaitez"), puis vous créez un autre convertisseur ou ajoutez un autre type à celui-là. J'espère que cela t'aides.

using System.Web.Script.Serialization;    
public class ExpandoJSONConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {         
        var result = new Dictionary<string, object>();
        var dictionary = obj as IDictionary<string, object>;
        foreach (var item in dictionary)
            result.Add(item.Key, item.Value);
        return result;
    }
    public override IEnumerable<Type> SupportedTypes
    {
        get 
        { 
              return new ReadOnlyCollection<Type>(new Type[] { typeof(System.Dynamic.ExpandoObject) });
        }
    }
}

Utilisation du convertisseur

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);
35

En utilisant JSON.NET, vous pouvez appeler SerializeObject pour "aplatir" l'objet expando:

dynamic expando = new ExpandoObject();
expando.name = "John Smith";
expando.age = 30;

var json = JsonConvert.SerializeObject(expando);

Sortira:

{"name":"John Smith","age":30}

Dans le contexte d'un contrôleur ASP.NET MVC, le résultat peut être renvoyé à l'aide de la méthode Content:

public class JsonController : Controller
{
    public ActionResult Data()
    {
        dynamic expando = new ExpandoObject();
        expando.name = "John Smith";
        expando.age = 30;

        var json = JsonConvert.SerializeObject(expando);

        return Content(json, "application/json");
    }
}
66
Mikael Koskinen

Voici ce que j'ai fait pour obtenir le comportement que vous décrivez:

dynamic expando = new ExpandoObject();
expando.Blah = 42;
expando.Foo = "test";
...

var d = expando as IDictionary<string, object>;
d.Add("SomeProp", SomeValueOrClass);

// After you've added the properties you would like.
d = d.ToDictionary(x => x.Key, x => x.Value);
return new JsonResult(d);

Le coût est que vous faites une copie des données avant de les sérialiser.

25
ajb

J'ai résolu cela en écrivant une méthode d'extension qui convertit le ExpandoObject en une chaîne JSON:

public static string Flatten(this ExpandoObject expando)
{
    StringBuilder sb = new StringBuilder();
    List<string> contents = new List<string>();
    var d = expando as IDictionary<string, object>;
    sb.Append("{");

    foreach (KeyValuePair<string, object> kvp in d) {
        contents.Add(String.Format("{0}: {1}", kvp.Key,
           JsonConvert.SerializeObject(kvp.Value)));
    }
    sb.Append(String.Join(",", contents.ToArray()));

    sb.Append("}");

    return sb.ToString();
}

Cela utilise l'excellente bibliothèque Newtonsoft .

JsonResult ressemble alors à ceci:

return JsonResult(expando.Flatten());

Et cela est retourné au navigateur:

"{SomeProp: SomeValueOrClass}"

Et je peux l'utiliser en javascript en faisant cela (référencé ici ):

var obj = JSON.parse(myJsonString);

J'espère que ça aide!

10
TimDog

J'ai pu résoudre ce même problème en utilisant JsonFx .

        dynamic person = new System.Dynamic.ExpandoObject();
        person.FirstName  = "John";
        person.LastName   = "Doe";
        person.Address    = "1234 Home St";
        person.City       = "Home Town";
        person.State      = "CA";
        person.Zip        = "12345";

        var writer = new JsonFx.Json.JsonWriter();
        return writer.Write(person);

sortie:

{"FirstName": "John", "LastName": "Doe", "Address": "1234 Home St", "City": "Home Town", "State": "CA", "Zip": "12345 "}

5
Garfield

J'ai poussé le processus d'aplatissement un peu plus loin et vérifié les objets de liste, ce qui supprime le non-sens de la valeur clé. :)

public string Flatten(ExpandoObject expando)
    {
        StringBuilder sb = new StringBuilder();
        List<string> contents = new List<string>();
        var d = expando as IDictionary<string, object>;
        sb.Append("{ ");

        foreach (KeyValuePair<string, object> kvp in d)
        {       
            if (kvp.Value is ExpandoObject)
            {
                ExpandoObject expandoValue = (ExpandoObject)kvp.Value;
                StringBuilder expandoBuilder = new StringBuilder();
                expandoBuilder.Append(String.Format("\"{0}\":[", kvp.Key));

                String flat = Flatten(expandoValue);
                expandoBuilder.Append(flat);

                string expandoResult = expandoBuilder.ToString();
                // expandoResult = expandoResult.Remove(expandoResult.Length - 1);
                expandoResult += "]";
                contents.Add(expandoResult);
            }
            else if (kvp.Value is List<Object>)
            {
                List<Object> valueList = (List<Object>)kvp.Value;

                StringBuilder listBuilder = new StringBuilder();
                listBuilder.Append(String.Format("\"{0}\":[", kvp.Key));
                foreach (Object item in valueList)
                {
                    if (item is ExpandoObject)
                    {
                        String flat = Flatten(item as ExpandoObject);
                        listBuilder.Append(flat + ",");
                    }
                }

                string listResult = listBuilder.ToString();
                listResult = listResult.Remove(listResult.Length - 1);
                listResult += "]";
                contents.Add(listResult);

            }
            else
            { 
                contents.Add(String.Format("\"{0}\": {1}", kvp.Key,
                   JsonSerializer.Serialize(kvp.Value)));
            }
            //contents.Add("type: " + valueType);
        }
        sb.Append(String.Join(",", contents.ToArray()));

        sb.Append("}");

        return sb.ToString();
    }
4
JustEngland

Cela peut ne pas vous être utile, mais j'avais une exigence similaire, mais j'ai utilisé un SerializableDynamicObject

J'ai changé le nom du dictionnaire en "Fields" puis cela sérialise avec Json.Net pour produire json qui ressemble à:

{"Fields": {"Property1": "Value1", "Property2": "Value2" etc. où Property1 et Property2 sont des propriétés ajoutées dynamiquement - c'est-à-dire des clés de dictionnaire

Ce serait parfait si je pouvais me débarrasser de la propriété supplémentaire "Fields" qui encapsule le reste, mais j'ai travaillé autour de cette limitation.

Réponse déplacée de cette question sur demande

3
BonyT

C'est une réponse tardive, mais j'ai eu le même problème, et cette question m'a aidé à les résoudre. En résumé, j'ai pensé que je devrais publier mes résultats, dans l'espoir que cela accélère la mise en œuvre pour les autres.

Tout d'abord, ExpandoJsonResult, dont vous pouvez renvoyer une instance dans votre action. Ou vous pouvez remplacer la méthode Json dans votre contrôleur et y retourner.

public class ExpandoJsonResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
        response.ContentEncoding = ContentEncoding ?? response.ContentEncoding;

        if (Data != null)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoConverter() });
            response.Write(serializer.Serialize(Data));
        }
    }
}

Ensuite, le convertisseur (qui prend en charge à la fois la sérialisation et la désérialisation. Voir ci-dessous pour un exemple de désérialisation).

public class ExpandoConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    { return DictionaryToExpando(dictionary); }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    { return ((ExpandoObject)obj).ToDictionary(x => x.Key, x => x.Value); }

    public override IEnumerable<Type> SupportedTypes
    { get { return new ReadOnlyCollection<Type>(new Type[] { typeof(System.Dynamic.ExpandoObject) }); } }

    private ExpandoObject DictionaryToExpando(IDictionary<string, object> source)
    {
        var expandoObject = new ExpandoObject();
        var expandoDictionary = (IDictionary<string, object>)expandoObject;
        foreach (var kvp in source)
        {
            if (kvp.Value is IDictionary<string, object>) expandoDictionary.Add(kvp.Key, DictionaryToExpando((IDictionary<string, object>)kvp.Value));
            else if (kvp.Value is ICollection)
            {
                var valueList = new List<object>();
                foreach (var value in (ICollection)kvp.Value)
                {
                    if (value is IDictionary<string, object>) valueList.Add(DictionaryToExpando((IDictionary<string, object>)value));
                    else valueList.Add(value);
                }
                expandoDictionary.Add(kvp.Key, valueList);
            }
            else expandoDictionary.Add(kvp.Key, kvp.Value);
        }
        return expandoObject;
    }
}

Vous pouvez voir dans la classe ExpandoJsonResult comment l'utiliser pour la sérialisation. Pour désérialiser, créez le sérialiseur et enregistrez le convertisseur de la même manière, mais utilisez

dynamic _data = serializer.Deserialize<ExpandoObject>("Your JSON string");

Un grand merci à tous les participants qui m'ont aidé.

3
Skymt

En utilisant le retour d'un ExpandoObject dynamique à partir de WebApi dans ASP.Net 4, le formateur JSON par défaut semble aplatir ExpandoObjects en un simple objet JSON.

1
Joseph Gabriel

JsonResult utilise JavaScriptSerializer qui désérialise réellement (le béton) Dictionary<string, object> comme tu veux.

Il y a une surcharge de Dictionary<string, object> constructeur qui prend IDictionary<string, object>.

ExpandoObject implémente IDictionary<string, object> (Je pense que vous pouvez voir où je vais ici ...)

ExpandoObject à un niveau

dynamic expando = new ExpandoObject();

expando.hello = "hi";
expando.goodbye = "cya";

var dictionary = new Dictionary<string, object>(expando);

return this.Json(dictionary); // or new JsonResult { Data = dictionary };

Une ligne de code, en utilisant tous les types intégrés :)

ExpandoObjects imbriqués

Bien sûr, si vous imbriquez ExpandoObjects, vous devrez les convertir récursivement en Dictionary<string, object>s:

public static Dictionary<string, object> RecursivelyDictionary(
    IDictionary<string, object> dictionary)
{
    var concrete = new Dictionary<string, object>();

    foreach (var element in dictionary)
    {
        var cast = element.Value as IDictionary<string, object>;
        var value = cast == null ? element.Value : RecursivelyDictionary(cast);
        concrete.Add(element.Key, value);
    }

    return concrete;
}

votre code final devenant

dynamic expando = new ExpandoObject();
expando.hello = "hi";
expando.goodbye = "cya";
expando.world = new ExpandoObject();
expando.world.hello = "hello world";

var dictionary = RecursivelyDictionary(expando);

return this.Json(dictionary);
1
dav_i