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

C#.NetHttpwebrequestAnonymous Types

C# Problem Overview


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"

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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.

Solution 3 - C#

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

Solution 4 - C#

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

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

It also returns RouteValueDictionary. Note that it's static

Solution 5 - C#

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

Solution 6 - C#

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

Solution 7 - C#

@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.

Solution 8 - C#

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

Solution 9 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChris KookenView Question on Stackoverflow
Solution 1 - C#kbrimingtonView Answer on Stackoverflow
Solution 2 - C#GWBView Answer on Stackoverflow
Solution 3 - C#JohnView Answer on Stackoverflow
Solution 4 - C#XymanekView Answer on Stackoverflow
Solution 5 - C#Konstantin SalavatovView Answer on Stackoverflow
Solution 6 - C#Cetin BasozView Answer on Stackoverflow
Solution 7 - C#AndiihView Answer on Stackoverflow
Solution 8 - C#JordanView Answer on Stackoverflow
Solution 9 - C#ExtragoreyView Answer on Stackoverflow