3

I need to interact with a third party endpoint that only accepts form encoded payloads. The endpoint wants complex data types at this endpoint, meaning something like this (but form encoded, not JSON):

{
    "foo": "bar",
    "baz": {
        "zip": "zap"
    }
}

My googling and the docs for the endpoint indicates that this should be form encoded like this:

foo=bar&baz[zip]=zap

I am using HttpClient and I want to use FormUrlEncodedContent but when I do it is replacing my [] with escaped characters.

public class Tests
{
    [Fact]
    public void Test()
    {
        var content = new Dictionary<String, String>
        {
            { "foo", "bar" },
            { "baz[zip]", "zap" }
        };
        var formContent = new FormUrlEncodedContent(content);
        Assert.Equal("foo=bar&baz[zip]=zap", formContent.ReadAsStringAsync().Result);
    }
}

What I end up with instead is:

foo=bar&baz%5Bzip%5D=zap
Micah Zoltu
  • 6,764
  • 5
  • 44
  • 72

1 Answers1

1

There are two issues in your question. Number one:

My googling indicates that this should be form encoded like this:

foo=bar&baz[zip]=zap

No. There is no convention or standard that transforms a multi-dimension key-value structure into a single-dimension one.

If you think about it, such a transformation will become very unwieldy very quickly. Object semantics are a lot more expressive than URL-encoding semantics. FWIW, they can't even agree on how to encode plain arrays into URLs, even though that would easily be possible.

Since there is no standard, it comes down to setting up a convention that server and client can live with. The convention that would cause the least headache and the least chance for nasty bugs would be(*):

  • Serialize your object into JSON. This gives you a string.
  • Transfer that string as the value in an URL parameter.
  • Do the reverse on the receiving end.

So in JS you would do:

encodeURIComponent(JSON.stringify({
    "foo": "bar",
    "baz": {
        "zip": "zap"
    }
}));

which gives you

"%7B%22foo%22%3A%22bar%22%2C%22baz%22%3A%7B%22zip%22%3A%22zap%22%7D%7D"

This can be transferred safely as an URL parameter and processed with minimal effort.

For .NET you can pick from several serialization options, two of them being the DataContractJsonSerializer and the JavaScriptSerializer, discussed over here.

I would strongly recommend against rolling your own serialization scheme for this task.


Number two:

but when I do it is replacing my [] with escaped characters.

Of course. The keys in URL-encoded key-value pairs are subject to the same rules as the values. A pair like {"a&b": "c&d"} would be encoded as a%26b=c%26d. In fact, you could send it as %61%26%62=%63%26%64. In other words, URL-decoding values but forgetting about URL-decoding key names on the receiving end is a bug. So is forgetting about URL-encoding key names. A discussion about what characters can be used in what form in a URL is over here.


(*) Short of "transferring the data as Content-Type: application/json directly, which is preferable to squeezing it into a query string.

Community
  • 1
  • 1
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • As I mentioned in my question, I am interacting with a third party api so I don't get to choose the serialization format. Their docs have a curl example with `-d bar[zip]=zap` and Google suggests that while not standard, this is the most common mechanism for sending complex objects form encoded (I think jquery does it like this). I agree that the api shouldn't do this, but none the less I have to deal with it. – Micah Zoltu Oct 11 '15 at 17:16
  • As mentioned in the answer, there is no standard and everything is based on convention. No .NET function will give you that format out of the box. If your endpoint documents the convention it expects, you will need to write the appropriate serializer yourself. – Tomalak Oct 11 '15 at 18:00
  • @Micah I'm sorry that it's not what you would have liked to hear, however that doesn't change the fact that the answer is correct. :) – Tomalak Oct 13 '15 at 17:14
  • Add a new answer that answers the question directly or restructure this one to do so and I'll accept it. At the moment, this answer argues that the question is invalid which I disagree with. I agree with your conclusion that what I want to do is not possible, but I believe the question is valid and reasonable. In particular, remove the suggestion of using an alternative encoding format as the question stated up front that I am interacting with a third party endpoint. Ideally, state up front that getting the desired encoding out of FormUrlEncodedContent is not possible and why. – Micah Zoltu Oct 13 '15 at 18:55
  • Yes, I argue that the question is invalid because you started it based on wrong assumptions, i.e. that there is a standard of some sort for this and that a function implementing this standard exists in .NET. I did my best to correct those assumptions. I did not say what you want is not reasonable, I said you will have to implement it yourself. And I took the liberty of suggesting a sensible alternative, I will certainly not take out that suggestion. Other people who find this question might be under fewer restrictions to use it. – Tomalak Oct 13 '15 at 19:32
  • When doing my research before asking this question, I found many answers similar to yours suggesting a change in the serialization format. In my searching, if I would have come across this answer, read the first couple sentences, then skipped it because it opens by saying the same thing as all the rest, "change formats". If it opened with, "You can't do jQuery style complex object serialization with `FormUrlEncodedContent`" and had a suggestion of changing formats at the end I would have likely stopped my search and moved on to rolling my own (which unfortunately is what I had to do). – Micah Zoltu Oct 13 '15 at 22:01
  • You are still misunderstanding me. I did not say it was not possible or that you would "have to" change formats. I said there is no *standard*, and you must implement a convention yourself. I'm running out of ways to put this. I'm beginning to suspect that you have really skipped reading my answer because you did not like the first sentence - and straight went into arguing against it instead. It also seems you have skipped over the documentation of `$.param()` because it contains two fat yellow warnings that say basically the same thing I did in my answer. – Tomalak Oct 14 '15 at 03:58