Как сгладить ExpandoObject, возвращенный через JsonResult в asp.net mvc?
Мне очень нравится ExpandoObject
при компиляции динамического объекта на стороне сервера во время выполнения, но мне сложно сгладить эту вещь во время сериализации JSON. Во-первых, я создаю экземпляр объекта:
dynamic expando = new ExpandoObject();
var d = expando as IDictionary<string, object>;
expando.Add("SomeProp", SomeValueOrClass);
Пока все хорошо. В моем контроллере MVC я хочу, чтобы затем отправить это как JsonResult, поэтому я делаю это:
return new JsonResult(expando);
Это сериализует JSON в нижеследующем, потребляемом браузером:
[{"Key":"SomeProp", "Value": SomeValueOrClass}]
НО, мне бы очень хотелось увидеть это:
{SomeProp: SomeValueOrClass}
Я знаю, что могу добиться этого, если я использую dynamic
вместо ExpandoObject
- JsonResult
способен сериализовать свойства и значения dynamic
в один объект (без бизнеса Key или Value), , но причиной, по которой мне нужно использовать ExpandoObject
, является то, что я не знаю всех свойств, которые я хочу на объекте, до времени выполнения, и, насколько я знаю, я не могу динамически добавлять свойство к dynamic
без использования ExpandoObject
.
Мне, возможно, придется просеивать бизнес "Key", "Value" в своем javascript, но я надеялся выяснить это до отправки его клиенту. Спасибо за вашу помощь!
Ответы
Ответ 1
Вы также можете создать специальный JSONConverter, который работает только для ExpandoObject, а затем зарегистрировать его в экземпляре JavaScriptSerializer. Таким образом, вы можете сериализовать массивы expando, комбинации объектов expando и... до тех пор, пока не найдете другой тип объекта, который не будет правильно сериализоваться ( "так, как хотите" ), затем вы делаете другой конвертер или добавляете другой тип в вот этот. Надеюсь, это поможет.
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) });
}
}
}
Использование конвертера
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);
Ответ 2
Используя JSON.NET, вы можете вызвать SerializeObject для "сглаживания" объекта expando:
dynamic expando = new ExpandoObject();
expando.name = "John Smith";
expando.age = 30;
var json = JsonConvert.SerializeObject(expando);
Будет выводиться:
{"name":"John Smith","age":30}
В контексте ASP.NET MVC Controller результат может быть возвращен с использованием 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");
}
}
Ответ 3
Вот что я сделал для достижения описанного вами поведения:
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);
Стоимость заключается в том, что вы делаете копию данных перед ее сериализацией.
Ответ 4
Я решил это, написав метод расширения, который преобразует ExpandoObject в строку 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();
}
Здесь используется отличная библиотека Newtonsoft.
JsonResult выглядит следующим образом:
return JsonResult(expando.Flatten());
И это возвращается браузеру:
"{SomeProp: SomeValueOrClass}"
И я могу использовать его в javascript, выполнив это (ссылка здесь):
var obj = JSON.parse(myJsonString);
Надеюсь, это поможет!
Ответ 5
Я смог решить эту же проблему, используя 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);
выход:
{ "FirstName": "John", "LastName": "Doe", "Address": "1234 Home St", "Город": "Домашний город", "Состояние": "CA", "Zip": "12345" }
Ответ 6
Я сделал процесс сглаживания еще на один шаг и проверил объекты списка, который удаляет ключевое значение ерунды.:)
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();
}
Ответ 7
Это может быть вам не полезно, но у меня было аналогичное требование, но я использовал SerializableDynamicObject
Я изменил имя словаря на "Поля" , а затем сериализует Json.Net для создания json, который выглядит так:
{ "Поля" : { "Property1": "Value1", "Property2": "Value2" и т.д.
где Property1 и Property2 являются динамически добавленными свойствами - то есть словарные ключи
Было бы идеально, если бы я мог избавиться от дополнительного свойства "Поля" , которое инкапсулирует остальные, но я работал над этим ограничением.
Ответ перешел из этого вопроса по запросу
Ответ 8
Это поздний ответ, но у меня была та же проблема, и этот вопрос помог мне решить их.
В качестве резюме я подумал, что должен опубликовать мои результаты, в надежде, что это ускорит реализацию для других.
Сначала ExpandoJsonResult, в который вы можете вернуть экземпляр вашего действия. Или вы можете переопределить Json-метод в своем контроллере и вернуть его там.
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));
}
}
}
Затем преобразователь (который поддерживает как сериализацию, так и де-сериализацию. См. ниже пример того, как де-сериализовать).
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;
}
}
В классе ExpandoJsonResult вы можете увидеть, как использовать его для сериализации. Чтобы де-сериализовать, создайте сериализатор и зарегистрируйте конвертер таким же образом, но используйте
dynamic _data = serializer.Deserialize<ExpandoObject>("Your JSON string");
Большое спасибо всем участникам, которые помогли мне.
Ответ 9
Используя возвращаемый динамический ExpandoObject из WebApi в ASP.Net 4, форматирование JSON по умолчанию, похоже, сглаживает ExpandoObjects в простой объект JSON.
Ответ 10
JsonResult
использует JavaScriptSerializer
, который фактически десериализует (конкретный) Dictionary<string, object>
, как вы хотите.
Здесь возникает перегрузка конструктора Dictionary<string, object>
, который принимает IDictionary<string, object>
.
ExpandoObject
реализует IDictionary<string, object>
(я думаю, вы можете видеть, куда я иду сюда)
Единый уровень 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 };
Одна строка кода, использующая все встроенные типы:)
Вложенные ExpandoObjects
Конечно, если вы вложен ExpandoObject
, вам нужно будет рекурсивно преобразовать их все в 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;
}
ваш окончательный код становится
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);
Ответ 11
Кажется, что сериализатор переводит Expando в словарь и затем сериализует его (таким образом, бизнес Key/Value). Вы пробовали Deserializing в качестве словаря, а затем вернули его в Expando?
Ответ 12
У меня была такая же проблема, и я понял что-то довольно странное.
Если я это сделаю:
dynamic x = new ExpandoObject();
x.Prop1 = "xxx";
x.Prop2 = "yyy";
return Json
(
new
{
x.Prop1,
x.Prop2
}
);
Это работает, но только если мой метод использует атрибут HttpPost. Если я использую HttpGet, я получаю ошибку.
Поэтому мой ответ работает только на HttpPost. В моем случае это был вызов Ajax, поэтому я мог бы изменить HttpGet на HttpPost.