在C#中将匿名类型转换为键/值数组?


77

我有以下匿名类型:

new {data1 = "test1", data2 = "sam", data3 = "bob"}

我需要一种可以接受此方法的方法,并在数组或字典中输出键值对。

我的目标是将其用作HttpRequest中的发布数据,因此我最终将连接到以下字符串中:

"data1=test1&data2=sam&data3=bob"

Answers:


119

这只需要一点点反思即可完成。

var a = new { data1 = "test1", data2 = "sam", data3 = "bob" };
var type = a.GetType();
var props = type.GetProperties();
var pairs = props.Select(x => x.Name + "=" + x.GetValue(a, null)).ToArray();
var result = string.Join("&", pairs);

7
var dict = props.ToDictionary(x => x.Name,x => x.GetValue(a_source,null))
约旦

30
我们已经走了这么远...我们可以将它做成一个衬里:var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null));
nikib3ro

@ kape123,的确如此。实际上,最新的.NET版本不再需要调用ToArray(),这很好。无论如何,它的响应完全适合SO而无需自动换行,因此我将其保持原样。
kbrimington

@ kape123的答案非常有效,到目前为止,我找不到任何问题。kbrimington,您可以(或者我应该)将此添加到答案中吗?
Xan-Kun Clark-Davis

@ Xan-KunClark-Davis,kape的回答很好;但是,答案实际上是相同的。这就是为什么我赞成他的评论而不是将其纳入我的答复。如今,在最新的.NET Framework上,我将把整个内容打包成一个扩展方法。这将提高可重用性和清晰度。
kbrimington

62

如果您使用的是.NET 3.5 SP1或.NET 4,则可以(滥用)RouteValueDictionary此功能。它实现IDictionary<string, object>并具有一个构造函数,该构造函数接受object属性并将其转换为键值对。

这样,遍历键和值以构建查询字符串将变得很简单。


4
我之所以说“滥用”,是因为该类最初是为路由而设计的(或者至少它的名称和名称空间暗示了这一点)。但是,它不包含特定于路由的功能,并且已用于其他功能(例如将匿名对象转换为ASP.NET MVCHtmlHelper扩展方法中HTML属性的字典。)
GWB 2010年

我已经做到了这一点,但现在我需要从RouteValueDictionary返回到异常对象,有什么想法?
Joshy 2013年

1
这甚至不是滥用。Microsoft做到了。例如,当您调用HtmlHelper.TextBox ...时,您应该传递一个匿名类型来设置属性值。这实际上将导致Razor中的绑定错误(例如,尝试调用@ Html.Partial(“〜/ Shared / _PartialControl.cshtml”,新的{id =“ id”,value =“ value”}),这将引发绑定错误,即使在局部视图中声明了@model动态也是如此。TextBox方法在内部调用“公共静态RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)”,这将返回RouteValueDictionary。因此,您就可以了
。– Triynko

@Triynko只是意味着微软也在滥用它。将来,他们可能会更改功能以执行特定于路由的操作。我建议您根据源代码制作自己的实现,并给它一个更合适的名称。
尼克·科德

6
除非依赖您的代码库,否则它将强制依赖System.Web,否则可能不需要依赖。有点希望该类被更通用化,并在更高的位置/位于另一个名称空间中。
尼克·阿尔布雷希特

28

他们在RouteValueDictionary中的操作如下:

  private void AddValues(object values)
    {
        if (values != null)
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
            {
                object obj2 = descriptor.GetValue(values);
                this.Add(descriptor.Name, obj2);
            }
        }
    }

完整的源代码在这里:http : //pastebin.com/c1gQpBMG


我尝试使用pastebin中的代码,Visual Studio则说未实现许多Dictionary方法。我必须对IDictionary进行显式转换。我只是将几个“ this._dictionary”切换为“(((IDictionary <string,object>)this._dictionary)””
Walter Stabosz 2011年


2
using Newtonsoft.Json;
var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};
var encodedData = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data))

2
请为答案添加一些说明,仅代码的答案会浪费审阅者的时间,并且常常被误解,甚至会被删除。
Munim Munna

1

@kbrimington的解决方案提供了一个不错的扩展方法-我的情况返回了HtmlString

    public static System.Web.HtmlString ToHTMLAttributeString(this Object attributes)
    {
        var props = attributes.GetType().GetProperties();
        var pairs = props.Select(x => string.Format(@"{0}=""{1}""",x.Name,x.GetValue(attributes, null))).ToArray();
        return new HtmlString(string.Join(" ", pairs));
    }

我正在使用它将任意属性放到Razor MVC视图中。我从使用RouteValueDictionary的代码开始,然后在结果上循环,但这很整洁。


6
这已经在框中存在(至少现在已经存在):HtmlHelper.AnonymousObjectToHtmlAttributes
Andrew

1

我做了这样的事情:

public class ObjectDictionary : Dictionary<string, object>
{
    /// <summary>
    /// Construct.
    /// </summary>
    /// <param name="a_source">Source object.</param>
    public ObjectDictionary(object a_source)
        : base(ParseObject(a_source))
    {

    }

    /// <summary>
    /// Create a dictionary from the given object (<paramref name="a_source"/>).
    /// </summary>
    /// <param name="a_source">Source object.</param>
    /// <returns>Created dictionary.</returns>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="a_source"/> is null.</exception>
    private static IDictionary<String, Object> ParseObject(object a_source)
    {
        #region Argument Validation

        if (a_source == null)
            throw new ArgumentNullException("a_source");

        #endregion

        var type = a_source.GetType();
        var props = type.GetProperties();

        return props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null));
    }
}

1

在@GWB使用a的建议的基础上RouteValueDictionary,我编写了此递归函数以支持嵌套的匿名类型,并使用其父母的键为这些嵌套的参数添加前缀。

public static string EncodeHtmlRequestBody(object data, string parent = null) {
    var keyValuePairs = new List<string>();
    var dict = new RouteValueDictionary(data);

    foreach (var pair in dict) {
        string key = parent == null ? pair.Key : parent + "." + pair.Key;
        var type = pair.Value.GetType();
        if (type.IsPrimitive || type == typeof(decimal) || type == typeof(string)) {
            keyValuePairs.Add(key + "=" + Uri.EscapeDataString((string)pair.Value).Replace("%20", "+"));
        } else {
            keyValuePairs.Add(EncodeHtmlRequestBody(pair.Value, key));
        }
    }

    return String.Join("&", keyValuePairs);
}

用法示例:

var data = new {
    apiOperation = "AUTHORIZE",
    order = new {
        id = "order123",
        amount = "101.00",
        currency = "AUD"
    },
    transaction = new {
        id = "transaction123"
    },
    sourceOfFunds = new {
        type = "CARD",
        provided = new {
            card = new {
                expiry = new {
                    month = "1",
                    year = "20"
                },
                nameOnCard = "John Smith",
                number = "4444333322221111",
                securityCode = "123"
            }
        }
    }
};

string encodedData = EncodeHtmlRequestBody(data);

encodedData 变成:

"apiOperation=AUTHORIZE&order.id=order123&order.amount=101.00&order.currency=AUD&transaction.id=transaction123&sourceOfFunds.type=CARD&sourceOfFunds.provided.card.expiry.month=1&sourceOfFunds.provided.card.expiry.year=20&sourceOfFunds.provided.card.nameOnCard=John+Smith&sourceOfFunds.provided.card.number=4444333322221111&sourceOfFunds.provided.card.securityCode=123"

希望这可以帮助处于类似情况的其他人。

编辑:正如DrewG所指出的,这不支持数组。要正确地实现对具有匿名类型的任意嵌套数组的支持将是不平凡的,而且由于我所使用的API都没有接受数组(我不确定甚至没有标准化的方法可以使用形式编码序列化它们),如果您需要支持他们,我会留给你们。


注意,这不能正确处理数组。将会堆栈溢出。需要这样的东西if(type.IsArray){var arr = pair.Value as string []; if(arr!= null){foreach(ar中的var s){keyValuePairs.Add((key +“ [] =” + s).Replace(“”,“ +”))); }}}
DrewG

0

为时已晚,但是无论如何我都将其添加为更可靠的解决方案。我在这里看到的有一些问题(例如说DateTime不能正常工作)。因此,我建议先转换为json(Newtonsoft Json.Net):

var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};

var result = string.Join("&",
            JsonConvert.DeserializeObject<Dictionary<string, string>>(
            JsonConvert.SerializeObject(data))
            .Select(x => $"{x.Key}={x.Value}")
        );
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.