Як вирівняти ExpandoObject, повернутий через JsonResult в mpc asp.net?


95

Мені дуже подобається під 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, але я сподівався розібратися в цьому, перш ніж надсилати його клієнту. Спасибі за вашу допомогу!


9
Чому б не просто використовувати словник <рядок, об’єкт> замість ExpandoObject? Він автоматично серіалізується до потрібного формату, і ви в будь-якому разі використовуєте лише свій ExpandoObject як словник. Якщо ви хочете серіалізувати законні ExpandoObject, використовуючи "повернути новий JsonResult (d.ToDictionary (x => x.Key, x => x.Value));" підхід, мабуть, найкращий компроміс.
BrainSlugs83

Відповіді:


36

Ви також можете зробити спеціальний JSONConverter, який працює лише для ExpandoObject, а потім зареєструвати його в екземплярі JavaScriptSerializer. Таким чином ви могли б серіалізувати масиви експандо, комбінації об’єктів експандо та ..., поки ви не знайдете інший тип об'єкта, який не серіалізується правильно ("так, як вам потрібно"), тоді ви робите інший конвертер або додаєте інший тип до ось цей. Сподіваюся, це допомагає.

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
Це спрацювало чудово для моїх потреб. Якщо хтось хоче підключити якийсь код, NotImplementedExceptionщоб додати щось на кшталт serializer.Deserialize<ExpandoObject>(json);, @theburningmonk пропонує рішення, яке працювало для мене.
патрон

2
Чудова робота @ pablo. Чудовий приклад включення користувальницької програми серіалізації в рамки MVC!
пб.

Найпростіший спосіб, який я знайшов, це: новий JavaScriptSerializer (). Десеріалізувати <object> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); що ти думаєш?
kavain

Мій серіалізатор називається рекурсивно. Якщо я встановив RecursionLimit, я отримаю або перевищив помилку рекурсії, або помилку виключення переповнення стека. Що я повинен зробити? :(
Дханашрі

71

Використовуючи JSON.NET, ви можете викликати SerializeObject, щоб "згладити" об'єкт експандо:

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

1
Newtonsoft.Json ти маєш на увазі?
Айяш

3
newtonsoft.json має кращу обробку для рекурсивного розширення всередині розширень або словників та внутрішніх словників, поза вікном
Jone Polvora

26

Ось що я зробив для досягнення поведінки, яку ви описуєте:

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

Вартість полягає в тому, що ви робите копію даних, перш ніж їх серіалізувати.


Приємно. Ви також можете передавати динаміку на льоту: поверніть новий JsonResult (((ExpandoObject) someIncomingDynamicExpando) .ToDictionary (item => item.Key, item => item.Value))
joeriks

"extendo.Add" не працює для мене. Я вважаю, що в цьому випадку це "d.Add" (що працювало для мене).
Джастін

9
Тож зачекайте ... ви створюєте ExpandoObject, викладаєте його як словник, використовуєте його як словник, а потім, коли це недостатньо добре, перетворюєте його на словник ... ... чому б не просто використовувати словник у цей випадок? ... o_o
BrainSlugs83

5
ExpandoObjectДає набагато більшу гнучкість , ніж простий словник. Хоча приклад, наведений вище, не демонструє цього, ви можете використовувати динамічні функції, ExpandoObjectщоб додати властивості, які ви хочете мати у своєму JSON. Звичайний Dictioanryоб'єкт перетвориться на JSON без проблем, тому, зробивши перетворення, це простий O (n) спосіб перевести просту у використанні динаміку ExpandoObjectу формат, який можна JSONified. Ви маєте рацію, хоча приклад, наведений вище, буде переробкою переліку ExpandoObject; простий Dictionaryбуде набагато краще.
ajb

1
Такий підхід більше - створення копії не працює в будь-якому середовищі, але у мене є лише невеликі об'єкти, і Expando надається (незмінною) третьою стороною ....
Sebastian J.

12

Я вирішив це, написавши метод розширення, який перетворює 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);

Я сподіваюся, що це допомагає!


7
Не оцінюй це! Щоб уникнути проблем із безпекою, слід використовувати десеріалізатор JSON. Дивіться json2.js: json.org/js.html var o = JSON.parse (myJsonString);
Ленс Фішер

Мені подобається цей метод розширення. Приємно!
Ленс Фішер

3
-1: Виконання цього методу розширення, яке повертає рядок, не є правильним способом інтерфейсу цієї поведінки з рамкою. Натомість слід розширити вбудовану архітектуру серіалізації.
BrainSlugs83

1
Основним недоліком цього методу є відсутність рекурсії - якщо ви знаєте, що об’єкт верхнього рівня динамічний, і це все, це працює, але якщо динамічні об'єкти могли бути на будь-якому або кожному рівні поверненого дерева об'єктів, це не вдасться.
Кріс Москіні

Я зробив деякі вдосконалення цього методу, щоб зробити його рекурсивним. Ось код: gist.github.com/renanvieira/e26dc34e2de156723f79
MaltMaster

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", "City": "Home Town", "State": "CA", "Zip": "12345 "}


1
Ви також можете це зробити за допомогою JSON .Net (Newtonsoft), виконавши наступні кроки. var образу = людина як об'єкт; var json = JsonConvert.SerializeObject (сутність);
bkorzynski

4

Я зробив процес вирівнювання ще на крок і перевірив наявність об'єктів списку, який видаляє дурницю ключового значення. :)

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

3

Це може вам не стати в нагоді, але у мене була схожа вимога, але я використовував SerializableDynamicObject

Я змінив назву словника на "Поля", а потім це серіалізується з Json.Net, щоб створити json, який виглядає так:

{"Поля": {"Property1": "Value1", "Properties2": "Value2" і т.д., де Properties1 та Properties2 є динамічно доданими властивостями - тобто ключі словника

Було б ідеально, якби я міг позбутися зайвої властивості "Поля", яка інкапсулює решту, але я працював над цим обмеженням.

Відповідь перенесена з цього запиту на запит


3

Це пізня відповідь, але у мене була така ж проблема, і це питання допомогло мені вирішити їх. Як підсумок, я подумав, що слід опублікувати свої результати, сподіваючись, що це пришвидшить реалізацію для інших.

Спочатку 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");

Велике спасибі всім учасникам тут, які мені допомогли.


1

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


1

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

-2

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


1
Об'єкт Expando реалізує IDictionary <рядок, об'єкт>, тому я думаю, що тому JsonResult серіалізує його в масив пар ключ / значення. Я боюсь, що видання його як ідентифікатора і назад ще не допоможе.
TimDog

-2

Я просто мав ту саму проблему і зрозумів щось досить дивне. Якщо я:

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

Він працює, але лише якщо мій метод використовує атрибут HttpPost. Якщо я використовую HttpGet, я отримую помилку. Тож моя відповідь працює лише на HttpPost. У моєму випадку це був Ajax Call, щоб я міг змінити HttpGet від HttpPost.


2
-1 Це не дуже корисно, оскільки воно зводиться до stackoverflow.com/a/7042631/11635, і немає сенсу динамічно робити цей матеріал, якщо ви збираєтесь розвертатись і залежати від імен статично, як і ви. Випуск AllowGet є повністю ортогональним.
Ruben Bartelink
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.