How to flatten an ExpandoObject returned via JsonR

2019-01-04 17:55发布

I really like the ExpandoObject while compiling a server-side dynamic object at runtime, but I am having trouble flattening this thing out during JSON serialization. First, I instantiate the object:

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

So far so good. In my MVC controller, I want to then send this down as a JsonResult, so I do this:

return new JsonResult(expando);

This serializes the JSON into the below, to be consumed by the browser:

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

BUT, what I'd really like is to see this:

{SomeProp: SomeValueOrClass}

I know I can achieve this if I use dynamic instead of ExpandoObject -- JsonResult is able to serialize the dynamic properties and values into a single object (with no Key or Value business), but the reason I need to use ExpandoObject is because I don't know all of the properties I want on the object until runtime, and as far as I know, I cannot dynamically add a property to a dynamic without using an ExpandoObject.

I may have to sift through the "Key", "Value" business in my javascript, but I was hoping to figure this out prior to sending it to the client. Thanks for your help!

12条回答
该账号已被封号
2楼-- · 2019-01-04 18:32

Using JSON.NET you can call SerializeObject to "flatten" the expando object:

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

var json = JsonConvert.SerializeObject(expando);

Will output:

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

In the context of an ASP.NET MVC Controller, the result can be returned using the 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");
    }
}
查看更多
霸刀☆藐视天下
3楼-- · 2019-01-04 18:34

I took the flattening process one step further and checked for list objects, which removes the key value nonsense. :)

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();
    }
查看更多
姐就是有狂的资本
4楼-- · 2019-01-04 18:35

I was able to solve this same problem using 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);

output:

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

查看更多
forever°为你锁心
5楼-- · 2019-01-04 18:36

This is a late answer, but I had the same problem, and this question helped me solve them. As a summary, I thought I should post my results, in hopes that it speeds up the implementation for others.

First the ExpandoJsonResult, which you can return an instance of in your action. Or you can override the Json method in your controller and return it there.

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

Then the converter (which supports both serialization and de-serialization. See below for an example of how to de-serialize).

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

You can see in the ExpandoJsonResult class how to use it for serialization. To de-serialize, create the serializer and register the converter in the same way, but use

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

A big thank you, to all participants here that helped me.

查看更多
SAY GOODBYE
6楼-- · 2019-01-04 18:38

I solved this by writing an extension method that converts the ExpandoObject into a JSON string:

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

This uses the excellent Newtonsoft library.

JsonResult then looks like this:

return JsonResult(expando.Flatten());

And this is returned to the browser:

"{SomeProp: SomeValueOrClass}"

And I can use it in javascript by doing this (referenced here):

var obj = JSON.parse(myJsonString);

I hope this helps!

查看更多
何必那么认真
7楼-- · 2019-01-04 18:41

I just had the same problem and figured out something pretty weird. If I do:

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

It works, but only if my method use HttpPost attribute. If I use HttpGet i get error. So my answer works only on HttpPost. In my case it was an Ajax Call so i could change HttpGet by HttpPost.

查看更多
登录 后发表回答