Как сгладить 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 свойства и значения в один объект (без ключа или значение бизнес), но причина, по которой мне нужно использовать ExpandoObject это потому, что я не знаю все свойства, которые я хочу на объекте до времени выполнения и насколько я знаю, я не могу динамически добавлять свойства к dynamic без использования ExpandoObject.

возможно, мне придется просеять "ключ", "ценность" бизнеса в моем javascript, но я надеялся выяснить это до отправки его клиенту. Спасибо за вашу помощь!

12 85

12 ответов:

вы также можете создать специальный 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);

использование 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, результат может быть возвращен с помощью 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");
    }
}

вот что я сделал, чтобы добиться поведения, которое вы описываете:

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

стоимость заключается в том, что вы делаете копию данных перед сериализацией.

Я решил это, написав метод расширения, который преобразует 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);

Я надеюсь, что это помогает!

я смог решить эту же проблему с помощью 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", "City": "Home Town", "State": "CA", "Zip": "12345"}

Я сделал процесс выравнивания еще на один шаг и проверил объекты списка, которые удаляют бессмысленность значения ключа. :)

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

Это может быть не полезно для вас, но у меня было аналогичное требование, но я использовал SerializableDynamicObject

Я изменил имя словаря на "поля" , а затем это сериализуется с помощью Json.Net чтобы создать json, который выглядит так:

{"полей":{"свойство1":"значение1", "свойство2":"значение2" и т. д. где Property1 и Property2 динамически добавляются свойства - т. е. ключи словаря

было бы идеально, если бы я мог избавиться от дополнительного свойства " поля что инкапсулирует остальное, но я работал вокруг этого ограничения.

ответ переехала из этот вопрос на запрос

это поздний ответ, но у меня была та же проблема, и этот вопрос помог мне решить их. В качестве резюме я подумал, что должен опубликовать свои результаты, в надежде, что это ускорит реализацию для других.

сначала 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");

большое спасибо, всем участникам здесь, которые помогли мне.

с помощью возврата динамического ExpandoObject из WebApi в ASP.Net 4, форматер JSON по умолчанию, кажется, сглаживает ExpandoObjects в простой объект JSON.

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

конечно, если вы вложенности ExpandoObjects тогда вам нужно будет рекурсивно преобразовать их все в 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);

похоже, что сериализатор бросает Expando в словарь, а затем сериализует его (таким образом, бизнес ключа/значения). Вы пробовали десериализовать как словарь, а затем вернуть его в Expando?

У меня просто была такая же проблема, и я понял что-то довольно странное. Если я это сделаю:

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

это работает, но только если мой метод использует атрибут HttpPost. Если я использую HttpGet, я получаю ошибку. Поэтому мой ответ работает только на HttpPost. В моем случае это был вызов Ajax, поэтому я мог изменить HttpGet на HttpPost.