Làm thế nào để làm phẳng một ExpandoObject được trả về qua JsonResult trong asp.net mvc?


95

Tôi thực sự thích ExpandoObjecttrong khi biên dịch một đối tượng động phía máy chủ trong thời gian chạy, nhưng tôi gặp sự cố khi làm phẳng điều này trong quá trình tuần tự hóa JSON. Đầu tiên, tôi khởi tạo đối tượng:

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

Càng xa càng tốt. Trong bộ điều khiển MVC của tôi, sau đó tôi muốn gửi nó xuống dưới dạng JsonResult, vì vậy tôi thực hiện điều này:

return new JsonResult(expando);

Điều này tuần tự hóa JSON thành bên dưới, sẽ được trình duyệt sử dụng:

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

NHƯNG, điều tôi thực sự muốn là thấy điều này:

{SomeProp: SomeValueOrClass}

Tôi biết tôi có thể đạt được điều này nếu tôi sử dụng dynamicthay vì ExpandoObject- JsonResultcó thể tuần tự hóa các dynamicthuộc tính và giá trị thành một đối tượng duy nhất (không có nghiệp vụ Khóa hoặc Giá trị), nhưng lý do tôi cần sử dụng ExpandoObjectlà vì tôi không biết tất cả các thuộc tính tôi muốn trên đối tượng cho đến thời gian chạy và theo như tôi biết, tôi không thể thêm động một thuộc tính vào a dynamicmà không sử dụng an ExpandoObject.

Tôi có thể phải sàng lọc kinh doanh "Chìa khóa", "Giá trị" trong javascript của mình, nhưng tôi đã hy vọng tìm ra điều này trước khi gửi nó cho khách hàng. Cảm ơn bạn đã giúp đỡ!


9
Tại sao không chỉ sử dụng Dictionary <string, object> thay vì ExpandoObject? Nó tự động tuần tự hóa thành định dạng bạn muốn và dù sao thì bạn cũng chỉ sử dụng ExpandoObject của mình như một từ điển. Nếu bạn muốn tuần tự hóa ExpandoObject hợp pháp, hãy sử dụng "return new JsonResult (d.ToDictionary (x => x.Key, x => x.Value));" cách tiếp cận có lẽ là thỏa hiệp tốt nhất.
BrainSlugs83

Câu trả lời:


36

Bạn cũng có thể tạo một JSONConverter đặc biệt chỉ hoạt động cho ExpandoObject và sau đó đăng ký nó trong một phiên bản của JavaScriptSerializer. Bằng cách này, bạn có thể tuần tự hóa các mảng của expando, tổ hợp các đối tượng expando và ... cho đến khi bạn tìm thấy một loại đối tượng khác không được tuần tự hóa một cách chính xác ("theo cách bạn muốn"), sau đó bạn tạo một Bộ chuyển đổi khác hoặc thêm một loại khác vào cái này. Hi vọng điêu nay co ich.

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

Sử dụng công cụ chuyển đổi

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);

2
Điều này làm việc tuyệt vời cho nhu cầu của tôi. Nếu ai đó muốn cắm một số mã NotImplementedExceptionđể thêm một cái gì đó giống như serializer.Deserialize<ExpandoObject>(json);, @theburningmonk cung cấp một giải pháp phù hợp với tôi.
patridge

2
Công việc tuyệt vời @ pablo. Ví dụ tuyệt vời về việc cắm một quy trình tuần tự hóa tùy chỉnh vào khung MVC!
pb.

Cách dễ nhất mà tôi tìm thấy để làm điều này là: new JavaScriptSerializer (). Deserialize <object> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); bạn nghĩ sao?
kavain

Bộ nối tiếp của tôi được gọi một cách đệ quy. Nếu tôi đặt RecursionLimit, tôi sẽ nhận được lỗi vượt quá giới hạn đệ quy hoặc lỗi ngoại lệ tràn ngăn xếp. Tôi nên làm gì? :(
Dhanashree 25/07/17

71

Sử dụng JSON.NET, bạn có thể gọi SerializeObject để "làm phẳng" đối tượng expando:

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

var json = JsonConvert.SerializeObject(expando);

Sẽ xuất:

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

Trong ngữ cảnh của Bộ điều khiển ASP.NET MVC, kết quả có thể được trả về bằng phương thức 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
Ý bạn là Newtonsoft.Json?
Ayyash

3
newtonsoft.json đã tốt hơn xử lý cho expandos đệ quy bên expandos hoặc từ điển và từ điển bên trong, ra khỏi hộp
Jone Polvora

26

Đây là những gì tôi đã làm để đạt được hành vi mà bạn đang mô tả:

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

Cái giá phải trả là bạn đang tạo một bản sao dữ liệu trước khi tuần tự hóa nó.


Đẹp. Bạn cũng có thể truyền động một cách nhanh chóng: return JsonResult mới (((ExpandoObject) someIncomingDynamicExpando) .ToDictionary (item => item.Key, item => item.Value))
joeriks

"expando.Add" không hoạt động với tôi. Tôi tin rằng trong trường hợp này, nó là "d.Add" (đã làm việc cho tôi).
Justin

9
Vì vậy, đợi đã ... bạn đang tạo một ExpandoObject, truyền nó như một từ điển, sử dụng nó như một từ điển, và sau đó khi điều đó không đủ tốt, hãy chuyển đổi nó thành một từ điển ... ... tại sao không chỉ sử dụng một từ điển trong trường hợp này? ... o_o
BrainSlugs83

5
An ExpandoObjectcung cấp cho bạn sự linh hoạt hơn nhiều so với một Từ điển đơn giản. Mặc dù ví dụ trên không chứng minh điều đó, nhưng bạn có thể sử dụng các tính năng động của hàm ExpandoObjectđể thêm các thuộc tính mà bạn muốn có trong JSON của mình. Một Dictioanryđối tượng bình thường sẽ chuyển đổi sang JSON mà không gặp bất kỳ sự cố nào, vì vậy bằng cách thực hiện chuyển đổi, đó là một cách đơn giản O (n) để đưa động dễ sử dụng ExpandoObjectsang định dạng có thể được JSON hóa. Mặc dù vậy, bạn đúng, ví dụ trên sẽ là cách sử dụng rediculus của ExpandoObject; một đơn giản Dictionarysẽ tốt hơn nhiều.
ajb

1
Giống như cách tiếp cận nhiều hơn - tạo một bản sao không làm việc trong bất kỳ môi trường nhưng tôi có các đối tượng chỉ nhỏ và expando được cung cấp bởi một (không thể thay đổi) bên thứ 3 ....
Sebastian J.

12

Tôi đã giải quyết vấn đề này bằng cách viết một phương thức mở rộng để chuyển đổi ExpandoObject thành một chuỗi 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();
}

Điều này sử dụng thư viện Newtonsoft tuyệt vời .

JsonResult sau đó trông giống như sau:

return JsonResult(expando.Flatten());

Và điều này được trả lại cho trình duyệt:

"{SomeProp: SomeValueOrClass}"

Và tôi có thể sử dụng nó trong javascript bằng cách thực hiện điều này (tham khảo tại đây ):

var obj = JSON.parse(myJsonString);

Tôi hi vọng cái này giúp được!


7
Đừng đánh giá nó! Bạn nên sử dụng bộ giải mã JSON để tránh các vấn đề bảo mật. Xem json2.js: json.org/js.html var o = JSON.parse (myJsonString);
Lance Fisher

Tôi thích phương pháp mở rộng đó. Đẹp!
Lance Fisher

3
-1: Làm điều này trong một phương thức mở rộng trả về một chuỗi không phải là cách chính xác để giao tiếp hành vi này với khung. Thay vào đó, bạn nên mở rộng kiến ​​trúc tuần tự hóa được xây dựng.
BrainSlugs83

1
Hạn chế chính của phương pháp này là thiếu đệ quy - nếu bạn biết đối tượng cấp cao nhất là động và đó là nó, điều này sẽ hoạt động, nhưng nếu các đối tượng động có thể ở bất kỳ hoặc mọi cấp của cây đối tượng được trả về, thì điều này không thành công.
Chris Moschini

Tôi đã thực hiện một số cải tiến về phương pháp này để làm cho nó đệ quy. Đây là mã: gist.github.com/renanvieira/e26dc34e2de156723f79
MaltMaster

5

Tôi đã có thể giải quyết vấn đề tương tự này bằng cách sử dụng 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);

đầu ra:

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


1
Bạn cũng có thể thực hiện việc này bằng JSON .Net (Newtonsoft) bằng cách hoàn thành các bước sau. var entity = người với tư cách là đối tượng; var json = JsonConvert.SerializeObject (thực thể);
bkorzynski

4

Tôi đã tiến hành quá trình làm phẳng thêm một bước nữa và kiểm tra các đối tượng danh sách, điều này sẽ loại bỏ giá trị vô nghĩa của khóa. :)

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

Điều này có thể không hữu ích cho bạn, nhưng tôi đã có một yêu cầu tương tự, nhưng đã sử dụng SerializableDynamicObject

Tôi đã đổi tên của từ điển thành "Fields" và sau đó điều này tuần tự hóa với Json.Net để tạo ra json trông giống như:

{"Trường": {"Thuộc tính1": "Giá trị1", "Thuộc tính2": "Giá trị2", v.v. trong đó Thuộc tính1 và Thuộc tính2 là các thuộc tính được thêm động - tức là Khóa từ điển

Sẽ là hoàn hảo nếu tôi có thể loại bỏ thuộc tính "Fields" bổ sung đóng gói phần còn lại, nhưng tôi đã khắc phục hạn chế đó.

Câu trả lời được chuyển từ câu hỏi này theo yêu cầu


3

Đây là một câu trả lời muộn, nhưng tôi đã gặp vấn đề tương tự, và câu hỏi này đã giúp tôi giải quyết chúng. Tóm lại, tôi nghĩ mình nên đăng kết quả của mình, với hy vọng rằng nó sẽ đẩy nhanh quá trình triển khai cho những người khác.

Đầu tiên là ExpandoJsonResult, bạn có thể trả về một thể hiện trong hành động của mình. Hoặc bạn có thể ghi đè phương thức Json trong bộ điều khiển của mình và trả lại nó ở đó.

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

Sau đó, bộ chuyển đổi (hỗ trợ cả tuần tự hóa và hủy tuần tự hóa. Xem bên dưới để biết ví dụ về cách hủy tuần tự hóa).

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

Bạn có thể xem trong lớp ExpandoJsonResult cách sử dụng nó để tuần tự hóa. Để hủy tuần tự hóa, hãy tạo bộ tuần tự hóa và đăng ký bộ chuyển đổi theo cách tương tự, nhưng sử dụng

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

Xin gửi lời cảm ơn sâu sắc tới tất cả những người tham gia ở đây đã giúp đỡ tôi.


1

Sử dụng trả về ExpandoObject động từ WebApi trong ASP.Net 4, trình định dạng JSON mặc định dường như làm phẳng ExpandoObjects thành đối tượng JSON đơn giản.


1

JsonResultsử dụng JavaScriptSerializerthực sự deserializing (cụ thể) Dictionary<string, object>như bạn muốn.

Có một quá tải của hàm Dictionary<string, object>tạo IDictionary<string, object>.

ExpandoObjectdụng cụ IDictionary<string, object> (Tôi nghĩ bạn có thể thấy nơi tôi đang đi ở đây ...)

ExpandoObject cấp đơn

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

Một dòng mã, sử dụng tất cả các loại cài sẵn :)

ExpandoObjects lồng nhau

Tất nhiên nếu bạn đang lồng ExpandoObjectcác s thì bạn sẽ cần phải chuyển đổi đệ quy tất cả chúng thành 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;
}

mã cuối cùng của bạn trở thành

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

Có vẻ như bộ tuần tự hóa đang truyền Bản mở rộng sang Từ điển và sau đó tuần tự hóa nó (do đó là nghiệp vụ Khóa / Giá trị). Bạn đã thử Deserializing as a Dictionary rồi truyền nó trở lại Expando chưa?


1
Đối tượng Expando thực hiện IDictionary <string, object>, vì vậy tôi nghĩ đó là lý do tại sao JsonResult tuần tự hóa nó thành một mảng các cặp khóa / giá trị. Tôi e rằng việc đặt nó như một IDictionary và quay lại lần nữa sẽ không thực sự giúp làm phẳng nó.
TimDog

-2

Tôi vừa gặp vấn đề tương tự và phát hiện ra một điều khá kỳ lạ. Nếu tôi làm:

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

Nó hoạt động, nhưng chỉ khi phương pháp của tôi sử dụng thuộc tính HttpPost. Nếu tôi sử dụng HttpGet, tôi sẽ gặp lỗi. Vì vậy, câu trả lời của tôi chỉ hoạt động trên HttpPost. Trong trường hợp của tôi, đó là một Cuộc gọi Ajax nên tôi có thể thay đổi HttpGet bằng HttpPost.


2
-1 Điều này không thực sự hữu ích vì nó được tổng hợp thành stackoverflow.com/a/7042631/11635 và không có ích gì khi thực hiện công cụ này một cách động nếu bạn định quay lại và phụ thuộc vào các tên tĩnh như bạn làm. Vấn đề AllowGet là hoàn toàn trực giao.
Ruben Bartelink
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.