วิธีการแบน ExpandoObject ที่ส่งคืนผ่าน JsonResult ใน asp.net mvc


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เพราะฉันไม่รู้ทั้งหมด คุณสมบัติที่ฉันต้องการบนอ็อบเจ็กต์จนถึงรันไทม์และเท่าที่ฉันรู้ฉันไม่สามารถเพิ่มคุณสมบัติแบบไดนามิกให้กับ a dynamicโดยไม่ใช้ExpandoObject.

ฉันอาจต้องกรองธุรกิจ "คีย์" "มูลค่า" ในจาวาสคริปต์ของฉัน แต่ฉันหวังว่าจะได้ทราบก่อนที่จะส่งไปยังลูกค้า ขอบคุณสำหรับความช่วยเหลือของคุณ!


9
ทำไมไม่ใช้ Dictionary <string, object> แทน ExpandoObject ล่ะ? มันจะทำให้เป็นอนุกรมตามรูปแบบที่คุณต้องการโดยอัตโนมัติและคุณจะใช้งาน ExpandoObject เหมือนพจนานุกรมเท่านั้น หากคุณต้องการทำให้เป็นอนุกรมของ ExpandoObject ที่ถูกต้องโดยใช้ "return new JsonResult (d ToDictionary (x => x.Key, x => x.Value));" แนวทางน่าจะเป็นการประนีประนอมที่ดีที่สุด
BrainSlugs83

คำตอบ:


37

คุณสามารถสร้าง 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 เสนอวิธีแก้ปัญหาที่เหมาะกับฉัน
patridge

2
เยี่ยมมาก @ pablo ตัวอย่างที่ยอดเยี่ยมของการเสียบรูทีนซีเรียลไลเซชันที่กำหนดเองเข้ากับเฟรมเวิร์ก MVC!
pb.

วิธีที่ง่ายที่สุดที่ฉันพบคือ: JavaScriptSerializer ใหม่ () Deserialize <object> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); คุณคิดอย่างไร?
kavain

Serializer ของฉันถูกเรียกซ้ำ ถ้าฉันตั้งค่า RecursionLimit ฉันอาจได้รับขีด จำกัด การเรียกซ้ำเกินข้อผิดพลาดหรือข้อผิดพลาดข้อยกเว้นสแต็กโอเวอร์โฟลว์ ฉันควรทำอย่างไรดี? :(
Dhanashree

72

การใช้ 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-method:

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 คุณหมายถึง?
Ayyash

3
newtonsoft.json มีการจัดการที่ดีกว่าสำหรับ expandos แบบเรียกซ้ำภายในโปรแกรมขยายหรือพจนานุกรมและพจนานุกรมภายในโดยไม่ต้องแกะกล่อง
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

"expando.Add" ใช้ไม่ได้กับฉัน ฉันเชื่อว่าในกรณีนี้คือ "d.Add" (ซึ่งเหมาะกับฉัน)
Justin

9
เดี๋ยวก่อน ... คุณกำลังสร้าง ExpandoObject ส่งเป็นพจนานุกรมโดยใช้เหมือนพจนานุกรมแล้วเมื่อยังไม่ดีพอให้แปลงเป็นพจนานุกรม ... ... ทำไมไม่ใช้พจนานุกรมใน กรณีนี้? ... o_o
BrainSlugs83

5
An ExpandoObjectช่วยให้คุณมีความยืดหยุ่นมากกว่าพจนานุกรมทั่วไป แม้ว่าตัวอย่างข้างต้นจะไม่ได้แสดงให้เห็น แต่คุณสามารถใช้คุณสมบัติไดนามิกของExpandoObjectเพื่อเพิ่มคุณสมบัติที่คุณต้องการให้มีใน JSON ของคุณ Dictioanryออบเจ็กต์ปกติจะแปลงเป็น JSON โดยไม่มีปัญหาใด ๆ ดังนั้นการทำการแปลงจึงเป็นวิธี O (n) ง่ายๆในการใส่ไดนามิกที่ใช้งานง่ายExpandoObjectในรูปแบบที่สามารถเป็น JSONified ได้ คุณถูกต้องแม้ว่าตัวอย่างข้างต้นจะเป็นการใช้ rediculus ของ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}"

และฉันสามารถใช้ในจาวาสคริปต์ได้โดยทำสิ่งนี้ (อ้างอิงที่นี่ ):

var obj = JSON.parse(myJsonString);

ฉันหวังว่านี่จะช่วยได้!


7
อย่าประเมินเลย! คุณควรใช้ JSON deserializer เพื่อหลีกเลี่ยงปัญหาด้านความปลอดภัย ดู json2.js: json.org/js.html var o = JSON.parse (myJsonString);
Lance Fisher

ฉันชอบวิธีการขยายนั้น ดี!
Lance Fisher

3
-1: การทำเช่นนี้ในเมธอดส่วนขยายที่ส่งคืนสตริงไม่ใช่วิธีที่ถูกต้องในการเชื่อมต่อพฤติกรรมนี้กับเฟรมเวิร์ก คุณควรขยายสถาปัตยกรรมการทำให้เป็นอนุกรมในตัวแทน
BrainSlugs83

1
ข้อเสียเปรียบที่สำคัญของวิธีนี้คือการขาดการเรียกซ้ำ - ถ้าคุณรู้ว่าวัตถุระดับบนสุดเป็นแบบไดนามิกและนั่นก็เป็นเช่นนั้นสิ่งนี้ใช้ได้ แต่ถ้าวัตถุแบบไดนามิกสามารถส่งคืนในระดับใดก็ได้หรือทุกระดับของโครงสร้างวัตถุสิ่งนี้จะล้มเหลว
Chris Moschini

ฉันได้ทำการปรับปรุงวิธีนี้เพื่อทำให้เกิดซ้ำ นี่คือรหัส: 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 entity = บุคคลเป็นวัตถุ; 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

ฉันเปลี่ยนชื่อพจนานุกรมเป็น "Fields" จากนั้นก็ต่ออนุกรมกับ Json.Net เพื่อสร้าง json ซึ่งดูเหมือนว่า:

{"Fields": {"Property1": "Value1", "Property2": "Value2" เป็นต้นโดยที่ Property1 และ Property2 เป็นคุณสมบัติที่เพิ่มแบบไดนามิกเช่น Dictionary Keys

มันจะดีมากถ้าฉันสามารถกำจัดคุณสมบัติ "ฟิลด์" พิเศษที่ห่อหุ้มส่วนที่เหลือออกไปได้ แต่ฉันได้แก้ไขข้อ จำกัด นั้นแล้ว

คำตอบถูกย้ายจากคำถามนี้ตามคำขอ


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

ขอขอบคุณผู้เข้าร่วมทุกคนที่ช่วยฉัน


2

JsonResultใช้JavaScriptSerializerซึ่ง deserializes จริง (คอนกรีต) 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);

1

การใช้การส่งคืนไดนามิก ExpandoObject จาก WebApi ใน ASP.Net 4 รูปแบบ JSON เริ่มต้นดูเหมือนว่าจะแบน ExpandoObjects ให้เป็นออบเจ็กต์ JSON แบบธรรมดา


-2

ดูเหมือนว่า serializer กำลังแคสต์ Expando ไปยัง Dictionary จากนั้นจึงทำให้เป็นอนุกรม (เช่นธุรกิจ Key / Value) คุณเคยลอง Deserializing เป็นพจนานุกรมแล้วส่งกลับไปที่ Expando หรือไม่?


1
อ็อบเจ็กต์ Expando ใช้ IDictionary <string, object> ดังนั้นฉันคิดว่านั่นเป็นเหตุผลที่ JsonResult ทำให้เป็นอนุกรมเป็นคู่คีย์ / ค่า การแคสต์มันเป็น IDictionary และกลับมาอีกครั้งจะไม่ช่วยทำให้แบนราบได้จริงๆฉันกลัว
TimDog

-2

ฉันเพิ่งมีปัญหาเดียวกันและพบว่ามีอะไรแปลก ๆ ถ้าฉันทำ:

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

มันใช้ได้ แต่ถ้าวิธีของฉันใช้แอตทริบิวต์ HttpPost หากฉันใช้ HttpGet ฉันได้รับข้อผิดพลาด ดังนั้นคำตอบของฉันใช้ได้กับ HttpPost เท่านั้น ในกรณีของฉันมันเป็นการโทร Ajax ดังนั้นฉันจึงสามารถเปลี่ยน HttpGet โดย HttpPost


2
-1 สิ่งนี้ไม่มีประโยชน์จริง ๆ เนื่องจากมันเดือดไปที่stackoverflow.com/a/7042631/11635และไม่มีเหตุผลที่จะทำสิ่งนี้แบบไดนามิกหากคุณจะหันกลับมาและขึ้นอยู่กับชื่อแบบคงที่เหมือนที่คุณทำ ปัญหา AllowGet มีลักษณะตั้งฉากกันอย่างสมบูรณ์
Ruben Bartelink
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.