Alamofire: Multiple encoding methods in single request

Created on 6 Mar 2015  Â·  37Comments  Â·  Source: Alamofire/Alamofire

I need to make a POST request with an HTTP Body with a JSON object, but I also need to use url query parameters in the same request.

POST: http://www.mysite.com/api/create?param1=value&param2=value
HTTP Body: { foo : [ bar, foo], bar: foo}

Is this supported? How would I go about doing this?

Thanks

Most helpful comment

@cesar-oyarzun-m The way I solved this was to create an extension on NSURL for adding query parameters, then I just added the parameters to my URL before creating the Alamofire Request

extension NSURL {

  func URLByAppendingQueryParameters(parameters: [String: String]?) -> NSURL {
    guard let parameters = parameters,
      urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: true) else {
        return self
    }

    var mutableQueryItems: [NSURLQueryItem] = urlComponents.queryItems ?? []

    mutableQueryItems.appendContentsOf(parameters.map{ NSURLQueryItem(name: $0, value: $1) })

    urlComponents.queryItems = mutableQueryItems

    return urlComponents.URL!
  }

}

All 37 comments

I posted this on StackOverflow
http://stackoverflow.com/questions/28908680/multiple-encoding-types-for-alamofire-request

If it's already supported and I just need a usage example, then it should probably be answered there. If this is not a supported feature yet, it seems like something worth discussing implementing here.

At this point, I've decided to solve this by manually encoding an NSURLRequest with the URL parameters, retrieving the URL from that request, and using that to create the final request. I've created a function to return the query parameter encoded request:

    private func queryParameterEncodedRequestURL(urlString: String,
    values: [String]) -> NSURL {

      let URL = NSURL(string: urlString)
      var request = NSURLRequest(URL: URL)

      let parameters = [
      "param1": values[0]!,
      "param2": values[1]!
      ]

      let encoding = Alamofire.ParameterEncoding.URL
      (request, _) = encoding.encode(request, parameters: parameters)

      return (request.URL, nil)
    }

This works fine, but I would definitely like to see Alamofire support multiple encoding types more easily. This feels like a workaround to me.

@mattt What is your opinion on implementing a native way to handle multiple encodings?

Multiple parameter encodings isn't really a common convention for web applications, so it's not something I'd be interested to build any more specific support for. That said, ParameterEncoding, with it's Custom case and associated closure value can be used to compose a request without reimplementing either JSON body or URL query string encoding. The solution you described should work just fine.

@mattt I think it's not correct to name it ParameterEncoding.URL if it encodes parameters depending on request method.

@mxl Why not? It's encoded in the URL for HTTP methods that do not support am message body, or in the body as application/x-www-form-**urlencoded** (emphasis added) for HTTP methods that designate a message body. That's just HTTP.

@mattt Ok, then ParameterEncoding should be only responsible for encoding and not for defining the place where encoded parameters are putted. And for example another enum ParameterPlacement could define where parameters could be placed.
Second way is to define ParameterEncoding.URLQuery that url-encodes parameters and puts them always in query part of URL.
And the third way is to make query, queryComponents and escape functions public so we do not copy-paste your code in ParameterEncoding.Custom.
My application makes POST request to service with url parameter which specify id of resource and JSON body. It's common thing for REST APIs.

@mxl Way too complicated for my tastes. The way it works now is flexible enough to handle any use case (including the one described by this issue), without exposing unnecessary API surface area.

@mattt May be, but solution suggested by @AnthonyMDev and solutions in SO answers look more like workaround not like real and simple solution for this common case. I think that solution should be build in in Alamofire.
By the way who puts url-encoded parameters in request body? Isn't that bad taste?

@mxl It's not a common case. And there is not going to be a one-size-fits-all solution; it's always going to be as hacky as the web service you're developing against.

By the way who puts url-encoded parameters in request body?

Every modern web framework supports this. It's convention.

@mattt But Alamofire is not web (application) framework, am I right? It's http networking library for mobile applications which in modern world works with APIs designed in right way with JSON bodies and URLs that identifies resource. Url encoded POST request is mostly used for posting html form data. But in mobile applications there are no html web-forms and the only reason to use url-encoded body is compatibility with old web-services.
I still think that there is more flexible and not more complex way to do these things. I'll try to implement it.

@mxl URL form encoding of parameters is a required feature for web services that do not support JSON encoding. Not everything an app might connect to is shiny and new.

@mattt Totally agree! But why not to support modern web services out of box? That's what developers are expecting from shiny and new Alamofire :)
It would be awesome if there will be the way to define placement of url encoded parameters or separate ParameterEncoding for placing parameters always in URL for all request methods.
Sorry for bothering. I just think that this will save much time for other developers.

@mxl That's the thing—Alamofire does support modern web services out of the box. That's what it was designed to do, and what it does. I'm not particularly interested in discussing this feature any further at this time.

I have the same problem I need to send JSON in the body but I need to add parameter in the URL, Does alamofire support this or I need some work around

@cesar-oyarzun-m The way I solved this was to create an extension on NSURL for adding query parameters, then I just added the parameters to my URL before creating the Alamofire Request

extension NSURL {

  func URLByAppendingQueryParameters(parameters: [String: String]?) -> NSURL {
    guard let parameters = parameters,
      urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: true) else {
        return self
    }

    var mutableQueryItems: [NSURLQueryItem] = urlComponents.queryItems ?? []

    mutableQueryItems.appendContentsOf(parameters.map{ NSURLQueryItem(name: $0, value: $1) })

    urlComponents.queryItems = mutableQueryItems

    return urlComponents.URL!
  }

}

His this is my code I just need to add timeout in the request, the encoding is using the request and the parameters, that is my problem.

enum Router: URLRequestConvertible {
case broadcast(String,[String: AnyObject])
var method: Alamofire.Method {
    switch self {
    case .broadcast:
        return .POST
    }
}

var path: String {
    switch self {
        case .broadcast(let timeout):
            return "/api/networks/current/broadcasts?=\(timeout)"
    } 
}

var URLRequest: NSMutableURLRequest {
    let URL = NSURL(string: hostUrl)!
    let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
    mutableURLRequest.HTTPMethod = method.rawValue
    mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
    mutableURLRequest.setValue("Bearer \(tokenSDK)", forHTTPHeaderField: "Authorization")
    switch self {
        case .broadcast(let timeout,let parameters):
            let reqBroadcast = Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
            return reqBroadcast
  default:
            expLogging("EXP Http Request : \(mutableURLRequest)")
            return mutableURLRequest
    }
}
}

Your current code looks like it should work to me. I'm not sure what problem you are having using that. Can you please explain to me what the end result of your current method is, and why that isn't the desired result?

I still see my request like this
http://localhost:9000/api/networks/current/broadcasts0.000000=2000
I hardcode the timeout in the request

var path: String {
switch self {
    case .broadcast(let timeout):
        return "/api/networks/current/broadcasts?=2000"
} 
}

Do you have example how to use your code?

This may be due to the fact that your query parameter has no key. It's might be that somewhere in the parsing of the URL the query isn't recognized because it has no key and the parser is doing something weird.

I've never worked with query parameters that have a value and no key before, but, assuming you are designing the API, you may want to change if so that there is a 'timeout' parameter.

http://localhost:9000/api/networks/current/broadcasts?timeout=2000

sorry my mistake yes I miss the key, I still see the same issue http://localhost:9000/api/networks/current/broadcasts0.000000timeout=2000
PromiseKit: Unhandled Error: Error Domain=com.alamofire.error Code=-6006 "/api/networks/current/broadcasts%3Ftimeout=2000 does not exist" UserInfo={NSLocalizedFailureReason=/api/networks/current/broadcasts%3Ftimeout=2000 does not exist}

For some reason the encoding is breaking the URL, I think the problem is the line
URL.URLByAppendingPathComponent(path)

I fix this with by doing just this

  case .broadcast(let parameters):
            expLogging("EXP Http Request broadcast parameters: \(parameters)")
            let reqBroadcast = Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
            reqBroadcast.URL = NSURL(string: reqBroadcast.URLString+"?timeout=2000")
            return reqBroadcast

@AnthonyMDev Have you experience issues with JSON encoding ?, for some reason hasvalues after doing the encoding is transforming the string to different string
original value "channel": WwogICJzY2FsYSIsCiAgInRlc3QxIiwKICAwLAogIDEKXQ==,
value after JSON encoding "channel": "WyJzY2FsYSIsInRlc3QxIiwwLDFd"
I'm doing this Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0

@cesar-oyarzun-m No, I have not. These questions are better suited for StackOverflow. This closed issue is not really the appropriate place for them.

For future reference, it is worth noting that ParameterEncoding.URLEncodedInURL now exists.

BTW, you can mix URLEncodedInURL with JSON and possibly URLEncoded this way:
let rq = Alamofire.ParameterEncoding.URLEncodedInURL.encode(mutableURLRequest, parameters: baseParams).0 return Alamofire.ParameterEncoding.JSON.encode(rq, parameters: ["images": images]).0

Just as input and another opinion on the original discussion between @mattt (who didn't seem to be interested in this feature at all) and @mxl:

The JSON API standard which is self-quotedly "a specification for building APIs in JSON" uses the HTTP body for sending and receiving object data in a special JSON format. It also defines ways to include relationships, sort by fields or even filter by them. These actions MUST (if supported) be sent as "query parameter(s)" – or differently put they have to be URL encoded. See the sorting documentation as an example.

I'm currently writing an API client for an API which was designed for Apps and complies to JSON API v1.0 – one of the future standard formats in JSON APIs. Specifically the API uses this Rails gem as its implementation. And neither the specification nor the framework accept the above mentioned actions via Body parameters. So when I want to send data and do one of the defined actions at once I have to build the query part of the URL myself. Not only me, everybody who wants to use these features of such APIs in the future will need to.

I can't force anybody to change his opinion. But to me it seems the evidence that this feature is commonly requested and will be needed in the future to support modern APIs is undeniable.

@Dschee An interesting point. However, this may be achievable right now by writing a custom ParameterEncoding that can take the additional include, sort, or filter parameters in a static function. e.g. JSONAPI.include(_:sort:filter:) that can then be passed in Alamofire's request functions or used to encode URLRequests directly. However, concrete proposals or even PRs would help us further consider this request.

Personally, I don't like JSON API from an API design perspective, but I can see the usefulness of the functionality you described.

@Dschee While I agree with you that this seems to be a relatively commonly requested feature, there hasn't really been a proposal yet for an API to make this simple and clean. The workaround of using a custom ParameterEncoding is slightly tedious, but does work. I will try giving it a little bit more thought, but unless we come up with a way to do this without adding layers of complexity to the elegant APIs that Alamofire currently provides, I don't think we are going to get much support for this feature.

@jshier I am not a fan of JSON API myself either, but we have to consider that not all users will necessarily follow the same accepted practices. I can tell you that, if I was creating a web API, I would likely never have the need for encoding query parameters and a JSON body in the same request. However, I have to consume an API that was created by my company's web engineer, and he doesn't follow nearly ANY common practices for his API design (Our API isn't event RESTful). I regularly am consuming APIs where I am passing an object identifier into the query parameters and a dictionary of values to edit on the object in the JSON body.

While I agree that the commonly accepted practices SHOULD usually be followed, not everyone has the luxury of consuming APIs that follow them. From my perspective, Alamofire has two primary design goals.

  1. To provide a simple, elegant API for basic usage that abstracts away the complexities of URLSession.
  2. To provide the flexibility required for advanced usage, customization, and uncommon practices without compromising on point 1.

As I thought more about this, I'm realizing that the only use case I can see for "multiple encoding types" is adding query parameters and having an httpBody of some other encoding type (JSON, XML, etc.). You can't combine multiple types of encoding into the body of the request. Your Content-Type has to be set to one type, so this only makes sense with query parameters.

Does this sound right, or is there some use case I'm overlooking @Dschee? Tackling the problem of supporting any combination of encoding methods is very different from allowing query parameters with another encoding style in the body.

That sounds just right to me. I think it's the most common use case not already covered by Alamofire. I will try to add this feature to Alamofire in a backwards-compatible and clean way and submit a PR within the coming hours.

But your disliking of JSON API makes me wonder: What are you guys doing to make sure you don't have to reinvent the wheel again and again when designing APIs (e.g. sorting, pagination, including relationships in one request) and how are you trying to make sure client programmers have an easy time to consume many different APIs over time without rewriting a bunch of code everytime they consume a new one. JSON API is the closest thing to these goals that I know, but maybe I'm overseeing something? I'd be glad to learn from you guys, so please enlighten me. 😉

I've actually already started writing a proposal for how I would implement this. I'll finish it and post something in the morning.

I'm all for a consistent, stable format for APIs, I just think JSON API makes some poor decisions. One of them being the exact example you have when bringing JSON API into this discussion. :)

Haha okay, I see. :)

Well then, I'll wait for your post. Thanks for your help!

If you have a proposal for how you would implement this, please post it. I'd love to hear it. But I wouldn't spend too much time working on a PR until we have come to an agreement on how this should work.

I'm more of a guy who writes his proposals in PRs and marks them as [WIP] (work in progress). This way the code is already within the project and I can see if my suggestion makes any tests fail or not. Also I find it hard to discuss about specific lines using the threading style, a GitHub review is so much easier imho.

Sure, I can post my proposal PR until morning in Nevada (it's morning here in Germany 😉 ).

I've just posted my proposal as a PR in #1883. What do you think?

@Dschee Thanks! I've made my comments there. We should probably move discussion of how to handle this over to the PR since this issue has been closed already for some time.

Any updates on if this was included?

For reference, this does happen when you absorb other APIs.

I am doing a freelance contract for a medical company and they want to use this vendor who helps with HIPPA compliance.

https://docs.truevault.com/users#create-a-user

On their requests, often, they have both Query Params and Form Params, where I need two different types to handle this case.

Going to go with @AnthonyMDev to extend the URL for the work around for now

Was this page helpful?
0 / 5 - 0 ratings

Related issues

unusuallyreticent picture unusuallyreticent  Â·  23Comments

racer1988 picture racer1988  Â·  47Comments

kcharwood picture kcharwood  Â·  85Comments

kamzab95 picture kamzab95  Â·  36Comments

moritzsternemann picture moritzsternemann  Â·  39Comments