Bardzo podoba mi się ExpandoObject
podczas kompilowania obiektu dynamicznego po stronie serwera w czasie wykonywania, ale mam problem ze spłaszczeniem tego elementu podczas serializacji JSON. Najpierw tworzę instancję obiektu:
dynamic expando = new ExpandoObject();
var d = expando as IDictionary<string, object>;
expando.Add("SomeProp", SomeValueOrClass);
Na razie w porządku. W moim kontrolerze MVC chcę następnie wysłać to jako JsonResult, więc robię to:
return new JsonResult(expando);
Spowoduje to serializację JSON do poniższego, do wykorzystania przez przeglądarkę:
[{"Key":"SomeProp", "Value": SomeValueOrClass}]
ALE, naprawdę chciałbym to zobaczyć:
{SomeProp: SomeValueOrClass}
Wiem, że mogę to osiągnąć, jeśli dynamic
zamiast tego używam ExpandoObject
- JsonResult
jest w stanie serializować dynamic
właściwości i wartości w jednym obiekcie (bez biznesu kluczowego lub wartościowego), ale powodem, dla którego muszę użyć, ExpandoObject
jest to, że nie znam wszystkich właściwości, które chcę na obiekcie do czasu wykonania , i o ile wiem, nie mogę dynamicznie dodawać właściwości do obiektu dynamic
bez użycia pliku ExpandoObject
.
Być może będę musiał przejrzeć biznes „Klucz”, „Wartość” w moim javascript, ale miałem nadzieję, że uda mi się to rozgryźć przed wysłaniem go do klienta. Dzięki za pomoc!
źródło
Odpowiedzi:
Możesz również stworzyć specjalny JSONConverter, który działa tylko dla ExpandoObject, a następnie zarejestrować go w instancji JavaScriptSerializer. W ten sposób możesz serializować tablice expando, kombinacje obiektów expando i ... dopóki nie znajdziesz innego rodzaju obiektu, który nie jest serializowany poprawnie ("tak jak chcesz"), wtedy tworzysz kolejny konwerter lub dodajesz inny typ do ten. Mam nadzieję że to pomoże.
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) }); } } }
Korzystanie z konwertera
var serializer = new JavaScriptSerializer(); serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()}); var json = serializer.Serialize(obj);
źródło
NotImplementedException
aby dodać coś takiegoserializer.Deserialize<ExpandoObject>(json);
, @theburningmonk oferuje rozwiązanie, które działało dla mnie.Używając JSON.NET, możesz wywołać SerializeObject, aby „spłaszczyć” obiekt expando:
dynamic expando = new ExpandoObject(); expando.name = "John Smith"; expando.age = 30; var json = JsonConvert.SerializeObject(expando);
Wyświetli:
{"name":"John Smith","age":30}
W kontekście kontrolera ASP.NET MVC wynik można zwrócić przy użyciu metody 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"); } }
źródło
Oto, co zrobiłem, aby osiągnąć opisywane przez Ciebie zachowanie:
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);
Koszt jest taki, że tworzysz kopię danych przed ich serializacją.
źródło
ExpandoObject
zapewnia znacznie większą elastyczność niż prosty słownik. Chociaż powyższy przykład tego nie demonstruje, możesz użyć dynamicznych funkcji,ExpandoObject
aby dodać właściwości, które chcesz mieć w swoim formacie JSON. NormalnyDictioanry
obiekt zostanie przekonwertowany na JSON bez żadnych problemów, więc wykonując konwersję, jest to prosty sposób O (n) na umieszczenie łatwej w użyciu dynamikiExpandoObject
w formacie, który można zweryfikować za pomocą JSON. Masz jednak rację, powyższy przykład byłby wykorzystaniem rediculusExpandoObject
; prosteDictionary
byłoby znacznie lepsze.Rozwiązałem to, pisząc metodę rozszerzenia, która konwertuje ExpandoObject na ciąg 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(); }
Wykorzystuje to doskonałą bibliotekę Newtonsoft .
JsonResult wygląda wtedy następująco:
return JsonResult(expando.Flatten());
I to jest zwracane do przeglądarki:
"{SomeProp: SomeValueOrClass}"
I mogę go użyć w javascript, robiąc to (przywołane tutaj ):
var obj = JSON.parse(myJsonString);
Mam nadzieję, że to pomoże!
źródło
Udało mi się rozwiązać ten sam problem za pomocą 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);
wynik:
źródło
Poszedłem o krok dalej w procesie spłaszczania i sprawdziłem, czy nie ma obiektów na liście, co eliminuje nonsensowne wartości klucza. :)
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(); }
źródło
To może ci się nie przydać, ale miałem podobne wymaganie, ale użyłem SerializableDynamicObject
Zmieniłem nazwę słownika na „Fields”, a następnie serializuję go z Json.Net, aby utworzyć plik json, który wygląda następująco:
{"Pola": {"Właściwość1": "Wartość1", "Właściwość2": "Wartość2" itd. Gdzie Właściwość1 i Właściwość2 to właściwości dodawane dynamicznie - tj. Klucze słownika
Byłoby idealnie, gdybym mógł pozbyć się dodatkowej właściwości „Fields”, która obejmuje resztę, ale obejrzałem to ograniczenie.
Odpowiedź przeniesiona z tego pytania na żądanie
źródło
To późna odpowiedź, ale miałem ten sam problem, a to pytanie pomogło mi je rozwiązać. Podsumowując, pomyślałem, że powinienem opublikować swoje wyniki, mając nadzieję, że przyspieszy to wdrożenie u innych.
Najpierw ExpandoJsonResult, do którego możesz zwrócić instancję w swojej akcji. Lub możesz zastąpić metodę Json w kontrolerze i zwrócić ją tam.
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)); } } }
Następnie konwerter (który obsługuje zarówno serializację, jak i deserializację. Poniżej znajduje się przykład usuwania serializacji).
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; } }
W klasie ExpandoJsonResult można zobaczyć, jak używać go do serializacji. Aby usunąć serializację, utwórz serializator i zarejestruj konwerter w ten sam sposób, ale użyj
dynamic _data = serializer.Deserialize<ExpandoObject>("Your JSON string");
Wielkie podziękowania dla wszystkich uczestników, którzy mi pomogli.
źródło
JsonResult
używa,JavaScriptSerializer
które faktycznie deserializuje (konkret),Dictionary<string, object>
jak chcesz.Dictionary<string, object>
Konstruktor jest przeciążony, który pobieraIDictionary<string, object>
.ExpandoObject
narzędziaIDictionary<string, object>
(chyba widać, dokąd tu idę ...)Jednopoziomowy 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 };
Jedna linia kodu, wykorzystująca wszystkie wbudowane typy :)
Zagnieżdżone ExpandoObjects
Oczywiście, jeśli zagnieżdżasz
ExpandoObject
s, będziesz musiał rekurencyjnie przekonwertować je wszystkie naDictionary<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; }
Twój ostateczny kod staje się
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);
źródło
Korzystając z zwracania dynamicznego ExpandoObject z WebApi w ASP.Net 4, domyślny program formatujący JSON wydaje się spłaszczać ExpandoObjects do prostego obiektu JSON.
źródło
Wygląda na to, że serializator rzutuje Expando do słownika, a następnie serializuje go (czyli biznes klucz / wartość). Czy próbowałeś deserializacji jako słownika, a następnie odesłanie go z powrotem do Expando?
źródło
Po prostu miałem ten sam problem i wymyśliłem coś dziwnego. Jeśli zrobię:
dynamic x = new ExpandoObject(); x.Prop1 = "xxx"; x.Prop2 = "yyy"; return Json ( new { x.Prop1, x.Prop2 } );
Działa, ale tylko wtedy, gdy moja metoda używa atrybutu HttpPost. Jeśli używam HttpGet, pojawia się błąd. Więc moja odpowiedź działa tylko na HttpPost. W moim przypadku było to połączenie Ajax, więc mogłem zmienić HttpGet przez HttpPost.
źródło