Bagaimana cara meratakan ExpandoObject yang dikembalikan melalui JsonResult di asp.net MVC?

95

Saya sangat suka ExpandoObjectsaat menyusun objek dinamis sisi server saat runtime, tetapi saya mengalami masalah dalam meratakan hal ini selama serialisasi JSON. Pertama, saya memberi contoh objek:

dynamic expando = new ExpandoObject();
var d = expando as IDictionary<string, object>;
expando.Add("SomeProp", SomeValueOrClass);

Sejauh ini baik. Di pengontrol MVC saya, saya ingin mengirimkan ini sebagai JsonResult, jadi saya melakukan ini:

return new JsonResult(expando);

Ini membuat JSON menjadi serial di bawah ini, untuk digunakan oleh browser:

[{"Key":"SomeProp", "Value": SomeValueOrClass}]

TAPI, yang benar-benar saya inginkan adalah melihat ini:

{SomeProp: SomeValueOrClass}

Saya tahu saya dapat mencapai ini jika saya menggunakan dynamicalih-alih ExpandoObject- JsonResultdapat membuat serialisasi dynamicproperti dan nilai menjadi satu objek (tanpa bisnis Kunci atau Nilai), tetapi alasan saya perlu menggunakan ExpandoObjectadalah karena saya tidak tahu semua properti yang saya inginkan pada objek hingga runtime , dan sejauh yang saya tahu, saya tidak dapat secara dinamis menambahkan properti ke a dynamictanpa menggunakan ExpandoObject.

Saya mungkin harus menyaring bisnis "Kunci", "Nilai" di javascript saya, tetapi saya berharap untuk mencari tahu sebelum mengirimkannya ke klien. Terima kasih atas bantuan Anda!

TimDog
sumber
9
Mengapa tidak menggunakan Dictionary <string, object> saja daripada ExpandoObject? Ini secara otomatis membuat serial ke format yang Anda inginkan, dan Anda hanya menggunakan ExpandoObject seperti kamus. Jika Anda ingin membuat serial ExpandoObject yang sah, menggunakan "return new JsonResult (d.ToDictionary (x => x.Key, x => x.Value));" pendekatan mungkin merupakan kompromi terbaik.
BrainSlugs83

Jawaban:

37

Anda juga dapat membuat JSONConverter khusus yang hanya berfungsi untuk ExpandoObject lalu mendaftarkannya dalam sebuah instance JavaScriptSerializer. Dengan cara ini Anda dapat membuat serial array dari perluasan, kombinasi objek perluasan dan ... sampai Anda menemukan jenis objek lain yang tidak mendapatkan serialisasi dengan benar ("seperti yang Anda inginkan"), kemudian Anda membuat Konverter lain, atau menambahkan tipe lain ke yang ini. Semoga ini membantu.

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

Menggunakan konverter

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);
Donasi Pablo Rodda
sumber
2
Ini bekerja dengan baik untuk kebutuhan saya. Jika ada yang ingin memasukkan beberapa kode untuk NotImplementedExceptionmenambahkan sesuatu seperti serializer.Deserialize<ExpandoObject>(json);, @theburningmonk menawarkan solusi yang berhasil untuk saya.
patridge
2
Kerja bagus @ pablo. Contoh bagus dari memasukkan rutinitas serialisasi kustom ke dalam kerangka kerja MVC!
pb.
Cara termudah yang saya temukan untuk melakukan ini adalah: new JavaScriptSerializer (). Deserialize <object> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); Bagaimana menurut anda?
kavain
Serializer saya dipanggil secara rekursif. Jika saya menetapkan RecursionLimit, saya mendapatkan kesalahan batas rekursi melebihi atau kesalahan pengecualian stack overflow. Apa yang harus saya lakukan? :(
Dhanashree
71

Menggunakan JSON.NET Anda dapat memanggil SerializeObject untuk "meratakan" objek expando:

dynamic expando = new ExpandoObject();
expando.name = "John Smith";
expando.age = 30;

var json = JsonConvert.SerializeObject(expando);

Akan menghasilkan:

{"name":"John Smith","age":30}

Dalam konteks pengontrol ASP.NET MVC, hasil dapat dikembalikan menggunakan metode konten:

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");
    }
}
Mikael Koskinen
sumber
1
Newtonsoft.Json maksud Anda?
Ayyash
3
newtonsoft.json memiliki penanganan yang lebih baik untuk perluasan rekursif di dalam perluasan atau kamus dan kamus dalam, di luar kotak
Jone Polvora
26

Inilah yang saya lakukan untuk mencapai perilaku yang Anda gambarkan:

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

Biayanya adalah Anda membuat salinan data sebelum membuat serialisasi.

ajb
sumber
Bagus. Anda juga dapat mentransmisikan dinamika dengan cepat: return new JsonResult (((ExpandoObject) someIncomingDynamicExpando) .ToDictionary (item => item.Key, item => item.Value))
joeriks
"expando.Add" tidak berfungsi untuk saya. Saya percaya dalam hal ini "d.Add" (yang berhasil untuk saya).
Justin
9
Jadi tunggu ... Anda membuat ExpandoObject, mentransmisikannya sebagai kamus, menggunakannya seperti kamus, dan saat itu tidak cukup baik, mengubahnya menjadi kamus ... ... mengapa tidak menggunakan kamus saja kasus ini? ... o_o
BrainSlugs83
5
An ExpandoObjectmemberi Anda lebih banyak fleksibilitas daripada Kamus sederhana. Meskipun contoh di atas tidak mendemonstrasikannya, Anda dapat menggunakan fitur dinamis dari ExpandoObjectuntuk menambahkan properti yang ingin Anda miliki di JSON Anda. DictioanryObjek normal akan dikonversi ke JSON tanpa masalah, jadi dengan melakukan konversi, ini adalah cara sederhana O (n) untuk menempatkan dinamika yang mudah digunakan ExpandoObjectke dalam format yang dapat JSONified. Anda benar, contoh di atas akan menjadi penggunaan redikulus dari ExpandoObject; yang sederhana Dictionaryakan jauh lebih baik.
ajb
1
Lebih menyukai pendekatan itu - membuat salinan tidak berfungsi di lingkungan mana pun tetapi saya hanya memiliki objek kecil dan Expando disediakan oleh pihak ke-3 (tidak dapat diubah) ....
Sebastian J.
12

Saya menyelesaikan ini dengan menulis metode ekstensi yang mengubah ExpandoObject menjadi string 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();
}

Ini menggunakan perpustakaan Newtonsoft yang sangat baik .

JsonResult kemudian terlihat seperti ini:

return JsonResult(expando.Flatten());

Dan ini dikembalikan ke browser:

"{SomeProp: SomeValueOrClass}"

Dan saya dapat menggunakannya dalam javascript dengan melakukan ini (direferensikan di sini ):

var obj = JSON.parse(myJsonString);

Saya harap ini membantu!

TimDog
sumber
7
Jangan mengevaluasinya! Anda harus menggunakan deserializer JSON untuk menghindari masalah keamanan. Lihat json2.js: json.org/js.html var o = JSON.parse (myJsonString);
Lance Fisher
Saya suka metode ekstensi itu. Bagus!
Lance Fisher
3
-1: Melakukan ini dalam metode ekstensi yang mengembalikan string bukanlah cara yang benar untuk menghubungkan perilaku ini dengan framework. Anda harus memperluas arsitektur serialisasi bawaan sebagai gantinya.
BrainSlugs83
1
Kelemahan utama dari metode ini adalah kurangnya rekursi - jika Anda mengetahui objek tingkat atas dinamis dan hanya itu, ini berfungsi, tetapi jika objek dinamis dapat berada di setiap atau setiap tingkat pohon objek yang dikembalikan, ini gagal.
Chris Moschini
Saya telah membuat beberapa perbaikan pada metode ini untuk membuatnya rekursif. Ini kodenya: gist.github.com/renanvieira/e26dc34e2de156723f79
MaltMaster
5

Saya dapat memecahkan masalah yang sama ini menggunakan 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);

keluaran:

{"FirstName": "John", "LastName": "Doe", "Address": "1234 Home St", "City": "Home Town", "State": "CA", "Zip": "12345 "}

Garfield
sumber
1
Anda juga dapat melakukan ini menggunakan JSON .Net (Newtonsoft) dengan menyelesaikan langkah-langkah berikut. var entity = orang sebagai objek; var json = JsonConvert.SerializeObject (entitas);
bkorzynski
4

Saya mengambil proses perataan satu langkah lebih jauh dan memeriksa objek daftar, yang menghilangkan omong kosong nilai kunci. :)

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

Ini mungkin tidak berguna bagi Anda, tetapi saya memiliki persyaratan yang serupa, tetapi menggunakan SerializableDynamicObject

Saya mengubah nama kamus menjadi "Fields" dan kemudian ini bersambung dengan Json.Net untuk menghasilkan json yang terlihat seperti:

{"Fields": {"Property1": "Value1", "Property2": "Value2" dll. Di mana Properti1 dan Properti2 adalah properti yang ditambahkan secara dinamis - yaitu Kunci Kamus

Akan sempurna jika saya bisa menyingkirkan properti "Fields" ekstra yang merangkum sisanya, tetapi saya telah mengatasi batasan itu.

Jawaban dipindahkan dari pertanyaan ini atas permintaan

BonyT
sumber
3

Ini adalah jawaban yang terlambat, tetapi saya memiliki masalah yang sama, dan pertanyaan ini membantu saya menyelesaikannya. Sebagai ringkasan, saya pikir saya harus memposting hasil saya, dengan harapan dapat mempercepat implementasi untuk orang lain.

Pertama, ExpandoJsonResult, yang dapat Anda kembalikan instance-nya dalam tindakan Anda. Atau Anda dapat mengganti metode Json di pengontrol Anda dan mengembalikannya ke sana.

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

Kemudian konverter (yang mendukung serialisasi dan de-serialisasi. Lihat di bawah untuk contoh cara de-serialisasi).

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

Anda dapat melihat di kelas ExpandoJsonResult cara menggunakannya untuk serialisasi. Untuk membatalkan serialisasi, buat serializer dan daftarkan konverter dengan cara yang sama, tetapi gunakan

dynamic _data = serializer.Deserialize<ExpandoObject>("Your JSON string");

Terima kasih banyak, untuk semua peserta disini yang telah membantu saya.

Skymt
sumber
1

Menggunakan mengembalikan ExpandoObject dinamis dari WebApi di ASP.Net 4, pemformat JSON default tampaknya meratakan ExpandoObjects menjadi objek JSON sederhana.

Joseph Gabriel
sumber
1

JsonResultpenggunaan JavaScriptSerializeryang benar-benar menghilangkan (beton) Dictionary<string, object>seperti yang Anda inginkan.

Ada kelebihan Dictionary<string, object>konstruktor yang membutuhkan IDictionary<string, object>.

ExpandoObjectimplements IDictionary<string, object> (Saya pikir Anda dapat melihat ke mana saya pergi di sini ...)

ExpandoObject tingkat tunggal

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

Satu baris kode, menggunakan semua tipe bawaan :)

ExpandoObjects bersarang

Tentu saja jika Anda bersarang ExpandoObjectmaka Anda harus secara rekursif mengonversi semuanya menjadi 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;
}

kode terakhir Anda menjadi

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);
dav_i
sumber
-2

Sepertinya pembuat serial mentransmisikan Expando ke Kamus dan kemudian membuat serial (jadi bisnis Kunci / Nilai). Sudahkah Anda mencoba Deserializing as a Dictionary dan kemudian mentransmisikannya kembali ke Expando?

Luke Foust
sumber
1
Objek Expando mengimplementasikan IDictionary <string, object>, jadi saya pikir itulah mengapa JsonResult membuat serialisasi menjadi array pasangan kunci / nilai. Mentransmisikannya sebagai IDictionary dan kembali lagi tidak akan membantu meratakannya, saya khawatir.
TimDog
-2

Saya baru saja mengalami masalah yang sama dan menemukan sesuatu yang sangat aneh. Jika aku melakukan:

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

Ini berfungsi, tetapi hanya jika metode saya menggunakan atribut HttpPost. Jika saya menggunakan HttpGet saya mendapatkan kesalahan. Jadi jawaban saya hanya berfungsi di HttpPost. Dalam kasus saya itu adalah Panggilan Ajax sehingga saya dapat mengubah HttpGet oleh HttpPost.

Rodrigo Manguinho
sumber
2
-1 Ini tidak terlalu berguna karena intinya stackoverflow.com/a/7042631/11635 dan tidak ada gunanya melakukan hal ini secara dinamis jika Anda akan berbalik dan bergantung pada nama secara statis seperti yang Anda lakukan. Masalah AllowGet sepenuhnya ortogonal.
Ruben Bartelink