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

9

81

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"
Ardehs answered 14/8, 2010 at 3:47 Comment(0)
A
126

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);
Almucantar answered 14/8, 2010 at 4:1 Comment(5)
var dict = props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null))Ointment
We got this far... we can make it a one liner: var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null));Mirage
@kape123, indeed. In fact, the latest .NET versions no longer require the call to ToArray(), which is nice. At any rate, the response as it stands fits nicely in SO without word wrapping, so I'll leave it as it is.Almucantar
The answer from @kape123 is working really well and I can't find any problems with it (so far). kbrimington, can you (or should I) add this to the answer?Muttonhead
@Xan-KunClark-Davis, kape's answer is fine; however, it is really the same answer. That is why I upvoted his comment rather than integrate it into my response. Nowadays, on the latest .NET Frameworks, I would take the whole thing and bundle it up as an extension method. That would improve both reusability and clarity.Almucantar
V
64

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.

Vegetal answered 14/8, 2010 at 4:11 Comment(7)
I say "abuse" because the class was originally designed for routing (or at least its name and namespace imply this). However, it contains no routing-specific functionality and is already used for other features (like converting anonymous objects to dictionaries for HTML attributes in the ASP.NET MVC HtmlHelper extension methods.Vegetal
I have done exactly this but now i need to go from the RouteValueDictionary back to the anomous object, any thoughts ??Inga
This isn't even an abuse of it.. Microsoft does it. For example, when you call HtmlHelper.TextBox... you're supposed to pass an anonymous type to set the attribute values. That will actually cause binding error in Razor (e.g. try calling @Html.Partial("~/Shared/_PartialControl.cshtml", new {id="id",value="value"}), and it will throw a binding error, even with @model dynamic declared in the partial view. The TextBox method internally calls "public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)", which is returning a RouteValueDictionary. So there you go.Hunan
@Hunan that just means Microsoft is abusing it as well. In the future if they may change the functionality to do something that is specific to routing. I would recommend making your own implementation based off the source and give it a more suitable name.Winfrid
@GWB, I say the opposite. The RouteValueDictionary was abused to be in System.Web instead of being in System.Net or System.Net.Http, in corefx.Eastnortheast
Except that depending on your code base, it would force a dependency on System.Web that might not otherwise be needed. Kind of wish the class was more genericised and sat higher up/in a different namespace.Quantum
Not in .NET 4.7.1 or .NET Standard.Eastnortheast
C
29

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

Corneille answered 14/8, 2010 at 4:34 Comment(1)
I tried to use the code from pastebin and Visual Studio was saying that a bunch of the Dictionary methods were not implemented. I had to do an explicit cast to IDictionary. I just switched a couple of the "this._dictionary" to "((IDictionary<string,object>)this._dictionary)"Gehlenite
M
5

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

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

It also returns RouteValueDictionary. Note that it's static

Mayotte answered 21/7, 2018 at 8:30 Comment(1)
according to learn.microsoft.com/en-us/previous-versions/aspnet/… "Replaces underscore characters (_) with hyphens (-) in the specified HTML attributes" in some cases this might be a problem.Matias
H
3
using Newtonsoft.Json;
var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};
var encodedData = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data))
Hofstetter answered 8/5, 2018 at 16:26 Comment(1)
Please add some explanation to the answer, code-only answers waste reviewer's time and is often misunderstood, can even get deleted.Tsunami
M
3

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}")
        );
Maximin answered 12/11, 2020 at 12:50 Comment(0)
O
1

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

Opportunity answered 30/1, 2011 at 18:43 Comment(1)
This already exists in the box (at least, it does now): HtmlHelper.AnonymousObjectToHtmlAttributesChandigarh
O
1

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));
    }
}
Ointment answered 19/2, 2015 at 19:32 Comment(0)
V
1

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.

Vishnu answered 19/4, 2017 at 0:27 Comment(1)
Heads up, this does not handle arrays properly. Will stack overflow. need something like this if (type.IsArray) { var arr = pair.Value as string[]; if (arr != null) { foreach (var s in arr) { keyValuePairs.Add((key + "[]=" + s).Replace(" ", "+")); } } }Premeditation

© 2022 - 2024 — McMap. All rights reserved.