How to flatten an ExpandoObject returned via JsonResult in asp.net mvc?
Asked Answered
A

12

100

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!

Association answered 1/3, 2011 at 15:39 Comment(1)
Why not just use Dictionary<string, object> instead of ExpandoObject? It automatically serializes to the format you want, and you're only using your ExpandoObject like a dictionary anyway. If you want to serialize legitimate ExpandoObject's, using the "return new JsonResult(d.ToDictionary(x=>x.Key, x=>x.Value));" approach is probably the best compromise.Scenery
W
37

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);
Worcester answered 6/5, 2011 at 14:48 Comment(4)
This worked great for my needs. If anyone wants to plug in some code for the NotImplementedException to add something like serializer.Deserialize<ExpandoObject>(json);, @theburningmonk offers a solution that worked for me.Welldressed
Great work @pablo.Excellent example of plugging a custom serialization routine into the MVC framework!Cabezon
The easiest way that I found to do this was: new JavaScriptSerializer().Deserialize<object>(Newtonsoft.Json.JsonConvert.SerializeObject(listOfExpandoObject)); what do you think?Backspace
My serializer gets called recursively. If I set RecursionLimit, I either get recursion limit exceeded error or stack overflow exception error. What should I do? :(Luckin
B
72

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");
    }
}
Batch answered 6/12, 2012 at 14:28 Comment(2)
Newtonsoft.Json you mean?Arrivederci
newtonsoft.json has better handling for recursive expandos inside expandos or dictionaries and inner dictionaries, out of the boxSwords
W
37

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);
Worcester answered 6/5, 2011 at 14:48 Comment(4)
This worked great for my needs. If anyone wants to plug in some code for the NotImplementedException to add something like serializer.Deserialize<ExpandoObject>(json);, @theburningmonk offers a solution that worked for me.Welldressed
Great work @pablo.Excellent example of plugging a custom serialization routine into the MVC framework!Cabezon
The easiest way that I found to do this was: new JavaScriptSerializer().Deserialize<object>(Newtonsoft.Json.JsonConvert.SerializeObject(listOfExpandoObject)); what do you think?Backspace
My serializer gets called recursively. If I set RecursionLimit, I either get recursion limit exceeded error or stack overflow exception error. What should I do? :(Luckin
J
27

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.

Junkman answered 28/11, 2011 at 18:48 Comment(5)
Nice. You can also cast the dynamic on the fly: return new JsonResult(((ExpandoObject)someIncomingDynamicExpando).ToDictionary(item => item.Key, item => item.Value))Vena
"expando.Add" does not work for me. I believe in this case it is "d.Add" (that worked for me).Diadem
So wait... you're creating an ExpandoObject, casting it as a dictionary, using it like a dictionary, and then when that's not good enough, converting it to a dictionary... ... why not just use a dictionary in this case? ... o_oScenery
An ExpandoObject gives you much more flexibility than a simple Dictionary. Although the example above does not demonstrate it, you can use the dynamic features of the ExpandoObject to add the properties that you want to have in your JSON. A normal Dictioanry object will convert to JSON without any problems, so by doing the conversion, it's a simple O(n) way to put the easy-to-use dynamic ExpandoObject in to a format that can be JSONified. You are correct though, the example above would be a rediculus use of the ExpandoObject; a simple Dictionary would be much better.Junkman
Like that approach more - creating a copy doesn't work in any environment but I have only small objects and the Expando is provided by an (unchangeable) 3rd party....Epact
A
11

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!

Association answered 1/3, 2011 at 17:12 Comment(5)
Don't eval it! You should use a JSON deserializer to avoid security issues. See json2.js: json.org/js.html var o = JSON.parse(myJsonString);Casabonne
I like that extension method though. Nice!Casabonne
-1: Doing this in an extension method that returns a string is not the correct way to interface this behavior with the framework. You should extend the built in serialization architecture instead.Scenery
Major drawback of this method is a lack of recursion - if you know the top-level object to be dynamic and that's it, this works, but if dynamic objects could be at any or every level of the object tree returned, this fails.Idzik
I've made some improvements on this method to make it recursive. Here's the code: gist.github.com/renanvieira/e26dc34e2de156723f79Kizzee
G
5

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

Gnarled answered 15/8, 2011 at 23:26 Comment(1)
You can also do this using JSON .Net (Newtonsoft) by completing the following steps. var entity = person as object; var json = JsonConvert.SerializeObject(entity);Nervine
D
4

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();
    }
Duodecimo answered 9/4, 2011 at 18:35 Comment(0)
N
4

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);
Norahnorbert answered 30/10, 2014 at 13:55 Comment(0)
S
3

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

Schoen answered 19/7, 2012 at 7:55 Comment(0)
S
3

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.

Softwood answered 20/7, 2012 at 12:39 Comment(0)
C
1

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

Chelate answered 12/9, 2013 at 15:18 Comment(0)
B
-2

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?

Beset answered 1/3, 2011 at 15:47 Comment(1)
The Expando object implements the IDictionary<string, object>, so I think that's why JsonResult serializes it into an array of key/value pairs. Casting it as an IDictionary and back again wouldn't really help flatten it, I'm afraid.Association
T
-2

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.

Tristichous answered 2/4, 2012 at 20:58 Comment(1)
-1 This is not really useful as it boils down to https://mcmap.net/q/212727/-how-can-i-serialize-dynamic-object-to-json-in-c-mvc-controller-action and there's no point doing this stuff dynamically if you're going to turn around and depend on the names statically as you do. The AllowGet issue is completely orthogonal.Kovach

© 2022 - 2024 — McMap. All rights reserved.