Wie kann ein über JsonResult in asp.net mvc zurückgegebenes ExpandoObject reduziert werden?

94

Ich mag das ExpandoObjectbeim Kompilieren eines serverseitigen dynamischen Objekts zur Laufzeit sehr, aber ich habe Probleme, dieses Ding während der JSON-Serialisierung zu reduzieren. Zuerst instanziiere ich das Objekt:

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

So weit, ist es gut. In meinem MVC-Controller möchte ich dies dann als JsonResult senden, also mache ich Folgendes:

return new JsonResult(expando);

Dadurch wird der JSON wie folgt serialisiert, damit er vom Browser verwendet werden kann:

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

ABER was ich wirklich gerne sehen würde ist:

{SomeProp: SomeValueOrClass}

Ich weiß, dass ich dies erreichen kann, wenn ich dynamicanstelle von ExpandoObject- JsonResultdie dynamicEigenschaften und Werte in ein einzelnes Objekt serialisieren kann (ohne Schlüssel- oder Wertgeschäft), aber der Grund, den ich verwenden muss, ExpandoObjectist, dass ich nicht alle kenne Die Eigenschaften, die ich bis zur Laufzeit für das Objekt haben möchte , und soweit ich weiß, kann ich eine Eigenschaft nicht dynamisch zu einer hinzufügen, dynamicohne eine zu verwenden ExpandoObject.

Ich muss möglicherweise das Geschäft "Schlüssel", "Wert" in meinem Javascript durchsehen, aber ich hatte gehofft, dies herauszufinden, bevor ich es an den Kunden sende. Danke für Ihre Hilfe!

TimDog
quelle
9
Warum nicht einfach Dictionary <string, object> anstelle von ExpandoObject verwenden? Es wird automatisch in das gewünschte Format serialisiert und Sie verwenden Ihr ExpandoObject ohnehin nur wie ein Wörterbuch. Wenn Sie legitime ExpandoObjects serialisieren möchten, verwenden Sie "return new JsonResult (d.ToDictionary (x => x.Key, x => x.Value))"; Ansatz ist wahrscheinlich der beste Kompromiss.
BrainSlugs83

Antworten:

36

Sie können auch einen speziellen JSONConverter erstellen, der nur für ExpandoObject funktioniert, und ihn dann in einer Instanz von JavaScriptSerializer registrieren. Auf diese Weise können Sie Arrays von expando, Kombinationen von expando-Objekten und ... serialisieren, bis Sie eine andere Art von Objekt finden, die nicht korrekt serialisiert wird ("wie Sie wollen"), dann einen anderen Konverter erstellen oder einen anderen Typ hinzufügen dieses. Hoffe das hilft.

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

Konverter verwenden

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);
Pablo Rodda Spenden
quelle
2
Dies funktionierte hervorragend für meine Bedürfnisse. Wenn jemand Code einfügen möchte, NotImplementedExceptionum so etwas hinzuzufügen serializer.Deserialize<ExpandoObject>(json);, bietet @theburningmonk eine Lösung , die für mich funktioniert hat.
Patridge
2
Tolle Arbeit @ pablo. Hervorragendes Beispiel für das Einfügen einer benutzerdefinierten Serialisierungsroutine in das MVC-Framework!
pb.
Der einfachste Weg, dies zu tun, war: new JavaScriptSerializer (). Deserialize <Objekt> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); Was denken Sie?
Kavain
Mein Serializer wird rekursiv aufgerufen. Wenn ich RecursionLimit einstelle, wird entweder der Fehler "Rekursionslimit überschritten" oder der Fehler "Stapelüberlauf-Ausnahme" angezeigt. Was soll ich machen? :(
Dhanashree
71

Mit JSON.NET können Sie SerializeObject aufrufen, um das expando-Objekt zu "reduzieren":

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

var json = JsonConvert.SerializeObject(expando);

Wird ausgegeben:

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

Im Kontext eines ASP.NET MVC-Controllers kann das Ergebnis mit der Content-Methode zurückgegeben werden:

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");
    }
}
Mikael Koskinen
quelle
1
Newtonsoft.Json meinst du?
Ayyash
3
newtonsoft.json hat eine bessere Handhabung für rekursive Erweiterungen in Erweiterungen oder Wörterbüchern und inneren Wörterbüchern, sofort einsatzbereit
Jone Polvora
26

Folgendes habe ich getan, um das von Ihnen beschriebene Verhalten zu erreichen:

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

Die Kosten sind, dass Sie eine Kopie der Daten erstellen, bevor Sie sie serialisieren.

ajb
quelle
Nett. Sie können die Dynamik auch im laufenden Betrieb umwandeln: Geben Sie neues JsonResult (((ExpandoObject) someIncomingDynamicExpando) .ToDictionary (item => item.Key, item => item.Value) zurück
joeriks
"expando.Add" funktioniert bei mir nicht. Ich glaube in diesem Fall ist es "d.Add" (das hat bei mir funktioniert).
Justin
9
Warten Sie also ... Sie erstellen ein ExpandoObject, wandeln es als Wörterbuch um, verwenden es wie ein Wörterbuch und konvertieren es dann, wenn dies nicht gut genug ist, in ein Wörterbuch ... ... warum nicht einfach ein Wörterbuch in dieser Fall? ... o_o
BrainSlugs83
5
An ExpandoObjectbietet Ihnen viel mehr Flexibilität als ein einfaches Wörterbuch. Obwohl das obige Beispiel dies nicht demonstriert, können Sie die dynamischen Funktionen von verwenden ExpandoObject, um die Eigenschaften hinzuzufügen, die Sie in Ihrem JSON haben möchten. Ein normales DictioanryObjekt wird problemlos in JSON konvertiert. Durch die Konvertierung können Sie die ExpandoObjectbenutzerfreundliche Dynamik auf einfache Weise in ein Format umwandeln, das JSON-fähig ist. Sie haben jedoch Recht, das obige Beispiel wäre eine Rediculus-Verwendung von ExpandoObject; ein einfaches Dictionarywäre viel besser.
Ajb
1
Mehr wie dieser Ansatz - das Erstellen einer Kopie funktioniert in keiner Umgebung, aber ich habe nur kleine Objekte und das Expando wird von einem (unveränderlichen) Dritten bereitgestellt ....
Sebastian J.
12

Ich habe dieses Problem gelöst, indem ich eine Erweiterungsmethode geschrieben habe, die das ExpandoObject in eine JSON-Zeichenfolge konvertiert:

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

Dies nutzt die ausgezeichnete Newtonsoft- Bibliothek.

JsonResult sieht dann so aus:

return JsonResult(expando.Flatten());

Und dies wird an den Browser zurückgegeben:

"{SomeProp: SomeValueOrClass}"

Und ich kann es in Javascript verwenden, indem ich dies tue ( hier referenziert ):

var obj = JSON.parse(myJsonString);

Ich hoffe das hilft!

TimDog
quelle
7
Bewerten Sie es nicht! Sie sollten einen JSON-Deserializer verwenden, um Sicherheitsprobleme zu vermeiden. Siehe json2.js: json.org/js.html var o = JSON.parse (myJsonString);
Lance Fisher
Ich mag diese Erweiterungsmethode. Nett!
Lance Fisher
3
-1: Dies in einer Erweiterungsmethode zu tun, die eine Zeichenfolge zurückgibt, ist nicht die richtige Methode, um dieses Verhalten mit dem Framework zu verbinden. Sie sollten stattdessen die integrierte Serialisierungsarchitektur erweitern.
BrainSlugs83
1
Der Hauptnachteil dieser Methode ist die fehlende Rekursion. Wenn Sie wissen, dass das Objekt der obersten Ebene dynamisch ist und das ist es, funktioniert dies. Wenn sich dynamische Objekte jedoch auf einer oder jeder Ebene des zurückgegebenen Objektbaums befinden, schlägt dies fehl.
Chris Moschini
Ich habe einige Verbesserungen an dieser Methode vorgenommen, um sie rekursiv zu machen. Hier ist der Code: gist.github.com/renanvieira/e26dc34e2de156723f79
MaltMaster
5

Ich konnte das gleiche Problem mit JsonFx lösen .

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

Ausgabe:

{"Vorname": "John", "Nachname": "Doe", "Adresse": "1234 Home St", "Stadt": "Heimatstadt", "Bundesstaat": "CA", "Postleitzahl": "12345" "}

Garfield
quelle
1
Sie können dies auch mit JSON .Net (Newtonsoft) tun, indem Sie die folgenden Schritte ausführen. var entity = Person als Objekt; var json = JsonConvert.SerializeObject (Entität);
Bkorzynski
4

Ich ging noch einen Schritt weiter und suchte nach Listenobjekten, wodurch der Schlüsselwert-Unsinn beseitigt wurde. :) :)

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();
    }
JustEngland
quelle
3

Dies mag für Sie nicht nützlich sein, aber ich hatte eine ähnliche Anforderung, verwendete jedoch ein SerializableDynamicObject

Ich habe den Namen des Wörterbuchs in "Felder" geändert und dies wird dann mit Json.Net serialisiert, um json zu erzeugen, das wie folgt aussieht:

{"Felder": {"Eigenschaft1": "Wert1", "Eigenschaft2": "Wert2" usw. wobei Eigenschaft1 und Eigenschaft2 dynamisch hinzugefügte Eigenschaften sind - dh Wörterbuchschlüssel

Es wäre perfekt, wenn ich die zusätzliche Eigenschaft "Felder" entfernen könnte, die den Rest einschließt, aber ich habe diese Einschränkung umgangen.

Die Antwort wurde auf Anfrage von dieser Frage verschoben

BonyT
quelle
3

Dies ist eine späte Antwort, aber ich hatte das gleiche Problem, und diese Frage half mir, sie zu lösen. Zusammenfassend dachte ich, ich sollte meine Ergebnisse veröffentlichen, in der Hoffnung, dass dies die Implementierung für andere beschleunigt.

Zuerst das ExpandoJsonResult, von dem Sie eine Instanz in Ihrer Aktion zurückgeben können. Oder Sie können die Json-Methode in Ihrem Controller überschreiben und dort zurückgeben.

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

Dann den Konverter (der sowohl Serialisierung als auch De-Serialisierung unterstützt. Ein Beispiel für die De-Serialisierung finden Sie weiter unten).

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

In der ExpandoJsonResult-Klasse können Sie sehen, wie Sie sie für die Serialisierung verwenden. Erstellen Sie zum De-Serialisieren den Serializer und registrieren Sie den Konverter auf die gleiche Weise, verwenden Sie ihn jedoch

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

Ein großes Dankeschön an alle Teilnehmer hier, die mir geholfen haben.

Skymt
quelle
1

Bei Verwendung der Rückgabe von dynamischem ExpandoObject von WebApi in ASP.Net 4 scheint der Standard-JSON-Formatierer ExpandoObjects in ein einfaches JSON-Objekt zu reduzieren.

Joseph Gabriel
quelle
1

JsonResultverwendet, JavaScriptSerializerdie tatsächlich (den Beton) deserialisieren, Dictionary<string, object>wie Sie wollen.

Es gibt eine Überlastung des Dictionary<string, object>Konstruktors, die benötigt wird IDictionary<string, object>.

ExpandoObjectGeräte IDictionary<string, object> (ich denke, Sie können sehen, wohin ich hier gehe ...)

Einstufiges ExpandoObject

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

Eine Codezeile mit allen integrierten Typen :)

Verschachtelte ExpandoObjects

Wenn Sie ExpandoObjects verschachteln, müssen Sie sie natürlich alle rekursiv in Dictionary<string, object>s konvertieren :

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

Ihr endgültiger Code wird

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);
dav_i
quelle
-2

Es scheint, als würde der Serializer das Expando in ein Wörterbuch umwandeln und es dann serialisieren (also das Schlüssel- / Wertgeschäft). Haben Sie versucht, Deserializing als Wörterbuch zu verwenden und dieses dann auf ein Expando zurückzuführen?

Luke Foust
quelle
1
Das Expando-Objekt implementiert das IDictionary <string, object>. Ich denke, deshalb serialisiert JsonResult es in ein Array von Schlüssel / Wert-Paaren. Ich fürchte, es als IDictionary und wieder zurück zu werfen würde nicht wirklich helfen, es zu verflachen.
TimDog
-2

Ich hatte nur das gleiche Problem und fand etwas ziemlich Seltsames heraus. Wenn ich mache:

dynamic x = new ExpandoObject();
x.Prop1 = "xxx";
x.Prop2 = "yyy";
return Json
(
    new
    {
        x.Prop1,
        x.Prop2
    }
);

Es funktioniert, aber nur, wenn meine Methode das HttpPost-Attribut verwendet. Wenn ich HttpGet benutze, erhalte ich eine Fehlermeldung. Meine Antwort funktioniert also nur auf HttpPost. In meinem Fall war es ein Ajax-Aufruf, damit ich HttpGet durch HttpPost ändern konnte.

Rodrigo Manguinho
quelle
2
-1 Dies ist nicht wirklich nützlich, da es sich um stackoverflow.com/a/7042631/11635 handelt und es keinen Sinn macht, dieses Zeug dynamisch zu machen, wenn Sie sich umdrehen und sich statisch auf die Namen verlassen, wie Sie es tun. Das AllowGet-Problem ist vollständig orthogonal.
Ruben Bartelink