简体   繁体   中英

How can I convert a JSON string to URL parameters (GET request)?

I have the following JSON which has to be converted to URL parameters for a GET request.

An example is given here , however due to the complexity of this object, there can be multiple line_items_attributes each with the given values as shown, I'm having difficulties passing on the correct one.

I've also tried to just serialize the JSON object and pass on that value but that did not solve the issue either.

{
    "purchase_invoice":
    {
        "date":"14/04/2015",
        "due_date":"14/04/2015",
        "contact_id":500,
        "contact_name":"TestContact",
        "reference":"TestReference",
        "line_items_attributes":[
            {
                "unit_price":10.00,
                "quantity":1,
                "description":"TestLineItemAttDesc",
                "tax_code_id":1,
                "ledger_account_id":501,
                "tax_rate_percentage":19.0,
                "tax_amount":1.60

            }]
    }
}

I've been searching for a while now but without much luck. Any insights are appreciated and most welcome!

This is calling an API which does not support the incoming data in JSON format, so doing this server-side or changing the web service to support data in JSON format is not possible.

x-www-form-urlencoded content is, essentially, a flat sequence of key/value tuples, and as explained in this answer to How do I use FormUrlEncodedContent for complex data types? by Tomalak , there is no canonical way to transform a hierarchical, nested key/value structure into a flat one.

Nevertheless, from the accepted answer to this question, this example from the Stripe API, and the question mentioned above, it seems that it is common to flatten parameters inside complex nested objects by surrounding their keys in brackets and appending them to the topmost key like so:

{
    { "purchase_invoice[date]", "14/04/2015" } 
    { "purchase_invoice[due_date]", "14/04/2015" } 
    { "purchase_invoice[contact_id]", "500" } 
    { "purchase_invoice[contact_name]", "TestContact" } 
    { "purchase_invoice[reference]", "TestReference" } 
    { "purchase_invoice[line_items_attributes][0][unit_price]", "10" } 
    { "purchase_invoice[line_items_attributes][0][quantity]", "1" } 
    { "purchase_invoice[line_items_attributes][0][description]", "TestLineItemAttDesc" } 
    { "purchase_invoice[line_items_attributes][0][tax_code_id]", "1" } 
    { "purchase_invoice[line_items_attributes][0][ledger_account_id]", "501" } 
    { "purchase_invoice[line_items_attributes][0][tax_rate_percentage]", "19" } 
    { "purchase_invoice[line_items_attributes][0][tax_amount]", "1.6" } 
}

If this is what you want, you can generate such key/value pairs with using the following extension methods:

public static partial class JsonExtensions
{
    public static string ToUrlEncodedQueryString(this JContainer container)
    {
        return container.ToQueryStringKeyValuePairs().ToUrlEncodedQueryString();
    }

    public static IEnumerable<KeyValuePair<string, string>> ToQueryStringKeyValuePairs(this JContainer container)
    {
        return container.Descendants()
            .OfType<JValue>()
            .Select(v => new KeyValuePair<string, string>(v.ToQueryStringParameterName(), (string)v));
    }

    public static string ToUrlEncodedQueryString(this IEnumerable<KeyValuePair<string, string>> pairs)
    {
        return string.Join("&", pairs.Select(p => HttpUtility.UrlEncode(p.Key) + "=" + HttpUtility.UrlEncode(p.Value)));
        //The following works but it seems heavy to construct and await a task just to built a string:
        //return new System.Net.Http.FormUrlEncodedContent(pairs).ReadAsStringAsync().Result;
        //The following works and eliminates allocation of one intermediate string per pair, but requires more code:
        //return pairs.Aggregate(new StringBuilder(), (sb, p) => (sb.Length > 0 ? sb.Append("&") : sb).Append(HttpUtility.UrlEncode(p.Key)).Append("=").Append(HttpUtility.UrlEncode(p.Value))).ToString();
        //Answers from https://stackoverflow.com/questions/3865975/namevaluecollection-to-url-query that use HttpUtility.ParseQueryString() are wrong because that class doesn't correctly escape the keys names.
    }

    public static string ToQueryStringParameterName(this JToken token)
    {
        // Loosely modeled on JToken.Path
        // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JToken.cs#L184
        // By https://github.com/JamesNK
        if (token == null || token.Parent == null)
            return string.Empty;
        var positions = new List<string>();
        for (JToken previous = null, current = token; current != null; previous = current, current = current.Parent)
        {
            switch (current)
            {
                case JProperty property:
                    positions.Add(property.Name);
                    break;
                case JArray array:
                case JConstructor constructor:
                    if (previous != null)
                        positions.Add(((IList<JToken>)current).IndexOf(previous).ToString(CultureInfo.InvariantCulture)); // Don't localize the indices!
                    break;
            }
        }
        var sb = new StringBuilder();
        for (var i = positions.Count - 1; i >= 0; i--)
        {
            var name = positions[i];
            // TODO: decide what should happen if the name contains the characters `[` or `]`.
            if (sb.Length == 0)
                sb.Append(name);
            else
                sb.Append('[').Append(name).Append(']');
        }

        return sb.ToString();
    }
}

Then if you have a JSON string, you can parse it into a LINQ-to-JSON JObject and generate the query string like so:

var obj = JObject.Parse(jsonString);
var queryString = obj.ToUrlEncodedQueryString();

Alternatively, if you have some hierarchical data model POCO, you can generate your JObject from the model using JObject.FromObject() :

var obj = JObject.FromObject(myModel);
var queryString = obj.ToUrlEncodedQueryString();

Demo fiddle here .

So the final URL would be easy to compute using any URL Encoding mechanism . In C#, we could do the following:

string json = "...";
string baseUrl = "http://bla.com/somepage?myJson="
string urlWithJson = baseUrl + System.Net.WebUtility.UrlEncode(json)

Is there any way you can POST the data or otherwise send a request body instead? It would seem slightly easier/cleaner.

Sounds like you need something which is x-www-form-urlencoded .

From your example, it would look like this:

purchase_invoice%5Bdate%5D=14%2F04%2F2015&purchase_invoice%5Bdue_date%5D=14%2F04%2F2015&purchase_invoice%5Bcontact_id%5D=500&purchase_invoice%5Bcontact_name%5D=TestContact&purchase_invoice%5Breference%5D=TestReference&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Bunit_price%5D=10&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Bquantity%5D=1&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Bdescription%5D=TestLineItemAttDesc&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Btax_code_id%5D=1&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Bledger_account_id%5D=501&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Btax_rate_percentage%5D=19&purchase_invoice%5Bline_items_attributes%5D%5B0%5D%5Btax_amount%5D=1.6

The best reference for this encoding that I'm aware of is the undocumented jQuery.param method on the jQuery JavaScript library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM