【问题标题】:How to flatten an ExpandoObject returned via JsonResult in asp.net mvc?如何在asp.net mvc中展平通过JsonResult返回的ExpandoObject?
【发布时间】:2011-03-01 15:39:06
【问题描述】:

在运行时编译服务器端动态对象时,我真的很喜欢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 不使用 ExpandoObject

我可能需要在我的 javascript 中筛选“Key”、“Value”业务,但我希望在将其发送给客户之前弄清楚这一点。感谢您的帮助!

【问题讨论】:

  • 为什么不直接使用 Dictionary 而不是 ExpandoObject?它会自动序列化为您想要的格式,并且您只是像字典一样使用 ExpandoObject。如果要序列化合法的 ExpandoObject,请使用“return new JsonResult(d.ToDictionary(x=>x.Key, x=>x.Value));”方法可能是最好的妥协。

标签: c# javascript asp.net-mvc json expandoobject


【解决方案1】:

使用 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");
    }
}

【讨论】:

  • 你的意思是Newtonsoft.Json?
  • newtonsoft.json 可以更好地处理expandos或字典和内部字典中的递归expandos,开箱即用
【解决方案2】:

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

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

【讨论】:

  • 这非常适合我的需求。如果有人想为NotImplementedException 插入一些代码以添加类似serializer.Deserialize&lt;ExpandoObject&gt;(json);、@theburningmonk offers a solution 之类的东西,这对我有用。
  • 伟大的工作@pablo。将自定义序列化例程插入 MVC 框架的优秀示例!
  • 我发现最简单的方法是:new JavaScriptSerializer().Deserialize(Newtonsoft.Json.JsonConvert.SerializeObject(listOfExpandoObject));你怎么看?
  • 我的序列化程序被递归调用。如果我设置 RecursionLimit,我会得到超出递归限制错误或堆栈溢出异常错误。我该怎么办? :(
【解决方案3】:

这是我为实现您所描述的行为所做的:

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

成本是您在序列化数据之前制作数据的副本。

【讨论】:

  • 不错。您还可以动态投射动态:return new JsonResult(((ExpandoObject)someIncomingDynamicExpando).ToDictionary(item => item.Key, item => item.Value))
  • "expando.Add" 对我不起作用。我相信在这种情况下它是“d.Add”(对我有用)。
  • 所以等等...您正在创建一个 ExpandoObject,将其转换为字典,像字典一样使用它,然后当这还不够好时,将其转换为字典... .. . 在这种情况下,为什么不直接使用字典呢? ... o_o
  • ExpandoObject 比简单的字典更灵活。尽管上面的示例没有演示它,但您可以使用 ExpandoObject 的动态功能来添加您希望在 JSON 中具有的属性。普通的Dictioanry 对象将毫无问题地转换为 JSON,因此通过进行转换,将易于使用的动态 ExpandoObject 转换为可以 JSON 化的格式是一种简单的 O(n) 方式。不过你是对的,上面的例子是ExpandoObject的rediculus使用;一个简单的Dictionary 会好很多。
  • 更喜欢这种方法 - 创建副本在任何环境中都不起作用,但我只有小对象,并且 Expando 由(不可更改的)第 3 方提供......
【解决方案4】:

我通过编写一个将 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中使用它(引用here):

var obj = JSON.parse(myJsonString);

我希望这会有所帮助!

【讨论】:

  • 不要评估它!您应该使用 JSON 反序列化程序来避免安全问题。见 json2.js:json.org/js.html var o = JSON.parse(myJsonString);
  • 我喜欢这种扩展方法。不错!
  • -1:在返回字符串的扩展方法中执行此操作不是将此行为与框架接口的正确方法。您应该改为扩展内置的序列化架构。
  • 此方法的主要缺点是缺少递归 - 如果您知道顶级对象是动态的,仅此而已,这是可行的,但如果动态对象可以位于返回对象树,失败。
  • 我对这种方法进行了一些改进,使其具有递归性。这是代码:gist.github.com/renanvieira/e26dc34e2de156723f79
【解决方案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", “城市”:“家乡”,“州”:“CA”,“邮编”:“12345”}

【讨论】:

  • 您也可以使用 JSON .Net (Newtonsoft) 完成以下步骤。 var entity = 人作为对象; var json = JsonConvert.SerializeObject(entity);
【解决方案6】:

我将扁平化过程更进一步,并检查了列表对象,从而消除了无意义的键值。 :)

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

【讨论】:

    【解决方案7】:

    这可能对你没有用,但我有类似的要求,但使用了 SerializableDynamicObject

    我将字典的名称更改为“字段”,然后使用 Json.Net 进行序列化以生成如下所示的 json:

    {“字段”:{“Property1”:“Value1”,“Property2”:“Value2”等。 其中 Property1 和 Property2 是动态添加的属性 - 即字典键

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

    应要求从this question 移出答案

    【讨论】:

      【解决方案8】:

      这是一个迟到的答案,但我遇到了同样的问题,这个问题帮助我解决了这些问题。 作为总结,我认为我应该发布我的结果,希望它可以加快其他人的实施。

      首先是 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");
      

      非常感谢这里所有帮助过我的参与者。

      【讨论】:

        【解决方案9】:

        JsonResult 使用 JavaScriptSerializer,它实际上反序列化(具体的)Dictionary&lt;string, object&gt;

        Dictionary&lt;string, object&gt; 构造函数有一个重载,它采用 IDictionary&lt;string, object&gt;

        ExpandoObject 实现IDictionary&lt;string, object&gt; (我想你可以在这里看到我要去哪里......)

        单级 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&lt;string, object&gt;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);
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

            【讨论】:

            • Expando 对象实现了 IDictionary,所以我认为这就是 JsonResult 将其序列化为键/值对数组的原因。恐怕将其作为 IDictionary 并再次返回并不会真正有助于使其变平。
            【解决方案12】:

            我刚刚遇到了同样的问题,并且发现了一些非常奇怪的东西。 如果我这样做:

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

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

            【讨论】:

            • -1 这并不是真的有用,因为它归结为stackoverflow.com/a/7042631/11635,如果您要转身并像您一样静态地依赖名称,那么动态地做这些事情是没有意义的。 AllowGet 问题是完全正交的。
            猜你喜欢
            • 1970-01-01
            • 2011-03-08
            • 2023-03-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多