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:17

Here's what I did to achieve the behavior you're describing:

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

The cost is that you're making a copy of the data before serializing it.

查看更多
狗以群分
3楼-- · 2019-01-04 18:20

JsonResult uses JavaScriptSerializer which actually deserializes (the concrete) Dictionary<string, object> as you want.

There's an overload of the Dictionary<string, object> constructor which takes IDictionary<string, object>.

ExpandoObject implements IDictionary<string, object> (I think you can see where I am going here...)

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

One line of code, using all built-in types :)

Nested ExpandoObjects

Of course if you are nesting ExpandoObjects then you will need to recursively convert them all into 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;
}

your final code becoming

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);
查看更多
孤傲高冷的网名
4楼-- · 2019-01-04 18:22

Using returning dynamic ExpandoObject from WebApi in ASP.Net 4, the default JSON formatter seems to flatten ExpandoObjects into simple JSON object.

查看更多
叼着烟拽天下
5楼-- · 2019-01-04 18:22

It seems like the serializer is casting the Expando to a Dictionary and then serializing it (thus the Key/Value business). Have you tried Deserializing as a Dictionary and then casting that back to an Expando?

查看更多
我想做一个坏孩纸
6楼-- · 2019-01-04 18:27

This may not be useful to you, but I had a similar requirement, but used a SerializableDynamicObject

I changed the name of the dictionary to "Fields" and then this serializes with Json.Net to produce json which looks like:

{"Fields":{"Property1":"Value1", "Property2":"Value2" etc. where Property1 and Property2 are Dynamically added properties - i.e. Dictionary Keys

It would be perfect if I could get rid of the extra "Fields" property which encapsulates the rest, but I've worked around that limitation.

Answer moved from this question on request

查看更多
来,给爷笑一个
7楼-- · 2019-01-04 18:30

You could also, make a special JSONConverter that works only for ExpandoObject and then register it in an instance of JavaScriptSerializer. This way you could serialize arrays of expando,combinations of expando objects and ... until you find another kind of object that is not getting serialized correctly("the way u want"), then you make another Converter, or add another type to this one. Hope this helps.

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

Using converter

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);
查看更多
登录 后发表回答