如何在ASP.NET MVC中展平通过JsonResult返回的ExpandoObject?


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是因为我不知道所有据我所知,直到运行时对象上需要的属性,据我所知,如果dynamic不使用,就无法向a动态添加属性ExpandoObject

我可能需要在javascript中筛选“键”,“值”业务,但是我希望在将其发送给客户端之前先弄清楚这一点。谢谢你的帮助!


9
为什么不只使用Dictionary <string,object>而不是ExpandoObject?它会自动序列化为您想要的格式,并且无论如何您仅像字典一样使用ExpandoObject。如果要序列化合法的ExpandoObject,请使用“返回新的JsonResult(d.ToDictionary(x => x.Key,x => x.Value));”。方法可能是最好的折衷方案。
BrainSlugs83 2012年

Answers:


36

您还可以制作一个仅适用于ExpandoObject的特殊JSONConverter,然后将其注册到JavaScriptSerializer的实例中。这样,您可以序列化expando的数组,expando对象的组合以及...,直到找到另一种未正确序列化的对象(“您想要的方式”),然后再制作一个Converter或添加另一个类型这个。希望这可以帮助。

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 提供了适用于我的解决方案
2011年

2
很棒的工作@pablo。将自定义序列化例程插入MVC框架的绝佳示例!
铅。

我发现执行此操作的最简单方法是:new JavaScriptSerializer()。Deserialize <object>(Newtonsoft.Json.JsonConvert.SerializeObject(listOfExpandoObject)); 你怎么看?
卡瓦因

我的序列化器被递归调用。如果设置RecursionLimit,则将得到递归限制超出错误或堆栈溢出异常错误。我该怎么办?:(
Dhanashree

71

使用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控制器的上下文中,可以使用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 2013年

3
newtonsoft.json具有更好的处理力,可以在开箱即用的字典或词典以及内部词典中更好地处理递归
expandos

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 2012年

“ expando.Add”不适用于我。我相信在这种情况下,它是“ d.Add”(对我有用)。
贾斯汀

9
所以等等...您正在创建一个ExpandoObject,将其转换为字典,将其像字典一样使用,然后在效果不佳时将其转换为字典... ...为什么不在其中使用字典这个案例?... o_o
BrainSlugs83

5
ExpandoObject简单的字典相比,An 给您更大的灵活性。尽管上面的示例没有进行演示,但是您可以使用的动态功能ExpandoObject来添加想要在JSON中具有的属性。普通Dictioanry对象将毫无问题地转换为JSON,因此通过进行转换,这是一种简单的O(n)方法,可将易于使用的动态方式转换为可以JSON化ExpandoObject的格式。您是正确的,但是上面的示例是对ExpandoObject; 的重新使用。简单Dictionary会更好。
2012年

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

而且我可以这样做(仅供参考使用它在JavaScript 这里):

var obj = JSON.parse(myJsonString);

我希望这有帮助!


7
不要评估!您应该使用JSON解串器以避免安全问题。参见json2.js:json.org/js.html var o = JSON.parse(myJsonString);
Lance Fisher

我虽然喜欢这种扩展方法。真好!
Lance Fisher

3
-1:在返回字符串的扩展方法中执行此操作不是将此行为与框架进行交互的正确方法。您应该改为扩展内置的序列化体系结构。
BrainSlugs83 2012年

1
这种方法的主要缺点是缺乏递归-如果您知道顶级对象是动态的,那么就可以了,但是,如果动态对象可以位于返回的对象树的任何或每个级别,则失败。
克里斯·莫斯基尼

我对该方法进行了一些改进以使其递归。这是代码: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实体=以人为对象;var json = JsonConvert.SerializeObject(entity);
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是动态添加的属性-即字典键

如果可以摆脱封装其余部分的多余“字段”属性,那将是完美的,但是我已经解决了这一限制。

根据要求从该问题移出的答案


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

非常感谢所有对我有帮助的参与者。


1

在ASP.Net 4中使用从WebApi返回动态ExpandoObject时,默认的JSON格式化程序似乎将ExpandoObjects简化为简单的JSON对象。


1

JsonResult使用JavaScriptSerializer实际根据需要反序列化(具体)的内容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

当然,如果要嵌套ExpandoObjects,则需要将它们全部递归转换为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);

-2

似乎序列化程序正在将Expando转换为Dictionary,然后进行序列化(因此是键/值业务)。您是否尝试过将反序列化为字典,然后将其投射回Expando?


1
Expando对象实现IDictionary <string,object>,所以我认为这就是JsonResult将其序列化为键/值对数组的原因。恐怕将其转换为IDictionary并再次返回并不能真正使它变平。
TimDog 2011年

-2

我只是遇到了同样的问题,并且发现了一些很奇怪的东西。如果我做:

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

它有效,但前提是我的方法使用HttpPost属性。如果我使用HttpGet我得到错误。因此,我的答案仅适用于HttpPost。在我的情况下,这是一个Ajax调用,因此我可以通过HttpPost更改HttpGet。


2
-1这并不是真正有用的,因为它归结为stackoverflow.com/a/7042631/11635,如果您要转过身来并像静态一样依赖于名称,则没有必要动态地执行此操作。AllowGet问题是完全正交的。
鲁宾·巴特林克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.