In c# convert anonymous type into key/value array?

I have the following anonymous type:

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

I need a method that will take this in, and output key value pairs in an array or dictionary.

My goal is to use this as post data in an HttpRequest so i will eventually concatenate in into the following string:

"data1=test1&data2=sam&data3=bob"
46937 次浏览

This takes just a tiny bit of reflection to accomplish.

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

If you are using .NET 3.5 SP1 or .NET 4, you can (ab)use RouteValueDictionary for this. It implements IDictionary<string, object> and has a constructor that accepts object and converts properties to key-value pairs.

It would then be trivial to loop through the keys and values to build your query string.

Here is how they do it in 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);
}
}
}

Full Source is here: http://pastebin.com/c1gQpBMG

@kbrimington's solution makes a nice extension method - my my case returning a 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));
}

I'm using it to drop arbitrary attributes into a Razor MVC view. I started with code using RouteValueDictionary and looping on the results but this is much neater.

I did something like this:

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

Building on @GWB's suggestion of using a RouteValueDictionary, I wrote this recursive function to support nested anonymous types, prefixing those nested parameters by their parents' keys.

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

Example usage:

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 becomes:

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

Hope this helps someone else in a similar situation.

Edit: As DrewG pointed out, this doesn't support arrays. To properly implement support for arbitrarily nested arrays with anonymous types would be non-trivial, and as none of the APIs I've used have accepted arrays either (I'm not sure there's even a standardised way of serialising them with form encoding), I'll leave that to you folks if you need to support them.

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

There is a built-in method of converting anonymous objects to dictionaries:

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

It also returns RouteValueDictionary. Note that it's static

It is too late but anyway I would add this for a more robust solution. The ones I see here have some kind of problems (like they wouldn't work right with say DateTime). For that reason, I suggest first converting to a 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}")
);