99

我真的很喜欢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属性和值序列化为单个对象(没有 Key 或 Value 业务),我可以实现这一点,但我需要使用的原因ExpandoObject是因为我不知道所有我想要在对象上的属性直到运行时,据我所知,我不能在dynamic不使用a 的情况下动态地将属性添加到 a ExpandoObject

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

4

12 回答 12

72

使用 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");
    }
}
于 2012-12-06T14:28:54.060 回答
37

您还可以制作一个仅适用于 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);
于 2011-05-06T14:48:17.260 回答
27

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

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

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

于 2011-11-28T18:48:23.310 回答
11

我通过编写将 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);

我希望这有帮助!

于 2011-03-01T17:12:41.777 回答
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 " }

于 2011-08-15T23:26:49.153 回答
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();
    }
于 2011-04-09T18:35:26.040 回答
3

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

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

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

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

应要求从该问题移出答案

于 2012-07-19T07:55:22.670 回答
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");

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

于 2012-07-20T12:39:25.053 回答
3

JsonResultJavaScriptSerializer根据需要实际反序列化(具体)的用途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);
于 2014-10-30T13:55:14.827 回答
1

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

于 2013-09-12T15:18:01.090 回答
-2

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

于 2011-03-01T15:47:54.873 回答
-2

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

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

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

于 2012-04-02T20:58:43.423 回答