Подтвердить что ты не робот

Как сгладить 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, но я надеялся выяснить это до отправки его клиенту. Спасибо за вашу помощь!

4b9b3361

Ответ 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.