Moya: Multiple ParameterEncoding

Created on 23 Nov 2015  Â·  11Comments  Â·  Source: Moya/Moya

I need to make a POST request with query string and json body. How can I pass each parameters to Moya?

question

Most helpful comment

Just a heads up:

Once my PR #1147 gets merged then a solution to this issue will be built into Moya via the new requestCompositeParameters(bodyParameters:bodyEncoding:urlParameters:) task case.

You will be able to write something like the following (instead of parameters and parameterEncoding):

var task: Task {
    switch self {
    case .profile(let username, let language):
        return .requestCompositeParameters(bodyParameters: ["username: username], bodyEncoding: JSONEncoding.default, urlParameters: ["lang": language])
    default:
        return .requestPlain
    }
}

All 11 comments

Hey there! Great question. We cover a bit about parameter encoding in the docs, but the short-and-simple answer is that you need to pass a custom endpointClosure when you create the provider. It takes a target, which you switch on to customize individual endpoints.

The parameter encoding you're describing is pretty custom – query string in the URL and a JSON body – so you should use the .Custom parameter encoding type. It takes a closure that modifies the NSURLRequest and returns it. You can see the source here.

Like I said, it's a pretty custom thing so I'm afraid to say we don't have many examples on it yet. We'll help you figure it out, though! And once we do, it would be :100: if you wanted to submit a pull request that adds some more examples to the docs based on what your experience :cake:

Thanks, I'll try it!

Hey, just following up! Anything I can to do help?

Like this?

let compositeParameterEncodingClosure: (URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?) = { request, parameters in
    guard let parameters = parameters else {
        return (request.URLRequest, nil)
    }

    let (queryRequest, _) = ParameterEncoding.URL.encode(request, parameters: parameters["query"] as! [String: AnyObject])
    let (bodyRequest, _) = ParameterEncoding.JSON.encode(request, parameters: parameters["body"] as! [String: AnyObject])
    let compositeRequest = queryRequest.mutableCopy() as! NSMutableURLRequest
    compositeRequest.HTTPBody = bodyRequest.HTTPBody
    return (compositeRequest, nil)
}

Looks good to me! I must admit I've not tried this before, but it looks like it should work :+1:

Thanks!

The current version of this solution is:

let compositeParameterEncodingRequestClosure(endpoint: Endpoint<YourTarget>, closure: NSURLRequest -> Void) -> Void {
    guard let parameters = endpoint.parameters else {
        return closure(endpoint.urlRequest)
    }

    let (queryRequest, _) = ParameterEncoding.URL.encode(endpoint.urlRequest, parameters: (parameters["query"] as! [String: AnyObject]))
    let (bodyRequest, _) = ParameterEncoding.JSON.encode(endpoint.urlRequest, parameters: (parameters["body"] as! [String: AnyObject]))
    let compositeRequest = queryRequest.mutableCopy() as! NSMutableURLRequest
    compositeRequest.HTTPBody = bodyRequest.HTTPBody
    return closure(compositeRequest)
}

and use it as the requestClosure of your provider.

I don't know why but I can't build my url with parameter["query"]
ex: url is example.com, parameter["query"] = ["q": "wane"]
after ParameterEncoding.URL.encode(endpoint.urlRequest, parameters: (parameters["query"] as! [String: AnyObject]))
the url will still be example.com but not example.com?q=wane

I end up change the code to

let compositeParameterEncodingClosure: (URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?) = { request, parameters in
    guard let parameters = parameters else {
        return (request.URLRequest, nil)
    }


    let (bodyRequest, _) = ParameterEncoding.JSON.encode(request, parameters: parameters["body"] as? [String: AnyObject])
    let compositeRequest = bodyRequest.mutableCopy() as! NSMutableURLRequest
    compositeRequest.HTTPBody = bodyRequest.HTTPBody
    if let query = parameters["query"] as? [String: String],
        let url = request.URLRequest.URL?.absoluteString,
        var urlComponent = NSURLComponents(string: url) {
        urlComponent.queryItems = query.flatMap {
            NSURLQueryItem.init(name: $0.0, value: $0.1)
        }
        compositeRequest.URL = urlComponent.URL
    }
    return (compositeRequest, nil)
}

This is my solution for Moya 8:

import Moya
import Alamofire

public struct CompositeEncoding: ParameterEncoding {

    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        guard let parameters = parameters else {
            return try urlRequest.asURLRequest()
        }

        let queryParamters = (parameters["query"] as! Parameters)
        let bodyParameters = (parameters["body"] as! Parameters)

        let queryRequest = try URLEncoding(destination: .queryString).encode(urlRequest, with: queryParamters)
        let bodyRequest = try JSONEncoding().encode(urlRequest, with: bodyParameters)

        var compositeRequest = bodyRequest
        compositeRequest.url = queryRequest.url
        return compositeRequest
    }
}

In case anyone wants to perform: [only Query] or [Query + Body] (application/json) request:

struct CompositeEncoding: ParameterEncoding {
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        guard let parameters = parameters else {
            return try urlRequest.asURLRequest()
        }

        let queryParameters = (parameters["query"] as! Parameters)
        let queryRequest = try URLEncoding(destination: .queryString).encode(urlRequest, with: queryParameters)

        if let body = parameters["body"] {
            let bodyParameters = (body as! Parameters)
            var bodyRequest = try JSONEncoding().encode(urlRequest, with: bodyParameters)

            bodyRequest.url = queryRequest.url
            return bodyRequest
        } else {
            return queryRequest
        }
    }
}

public var parameterEncoding: ParameterEncoding {
    return CompositeEncoding() // For all requests composite encoding.
}

public var parameters: [String: Any]? {
    var params:[String: Any] = [:]
    params["query"] = ["token": API_TOKEN, "appId":API_APP_ID, "appKey":API_APP_KEY]

    switch self {
        case .validatePhone(let phone):
            params["body"] = ["phone":phone] // Setting body attribute finally makes request application/json
            break
        default: break
    }
    return params
}

Just a heads up:

Once my PR #1147 gets merged then a solution to this issue will be built into Moya via the new requestCompositeParameters(bodyParameters:bodyEncoding:urlParameters:) task case.

You will be able to write something like the following (instead of parameters and parameterEncoding):

var task: Task {
    switch self {
    case .profile(let username, let language):
        return .requestCompositeParameters(bodyParameters: ["username: username], bodyEncoding: JSONEncoding.default, urlParameters: ["lang": language])
    default:
        return .requestPlain
    }
}
Was this page helpful?
0 / 5 - 0 ratings