I have to send array of dictionaries via POST request. For example:
materials: [[String, String]] = [
[
"material_id": 1,
"qty": 10
],
[
"material_id": 2,
"qty": 5
]
]
Alamofire.request sends the next post data:
materials => array(
[0] => array("material_id" => 1),
[1] => array("qty" => 10),
[2] => array("material_id" => 2),
[3] => array("qty" => 5),
)
I want receive that representation:
materials => array(
[0] => array(
"material_id" => 1,
"qty" => 10
),
[1] => array(
"material_id" => 2,
"qty" => 5
),
)
The problem was in append method. I have coded on PHP 5 years and forgoted that in Swift the indexes not automatically assigned like in PHP. So, my first bugged code was:
func getParameters() -> [[String: AnyObject]] {
var result = [[String: AnyObject]]()
for mmap in mmaps {
let material: [String: AnyObject] = [
"material_id": mmap.material.id,
"quantity": mmap.qty
]
result.append(material)
}
return result
}
The answer is hard assign the keys as you need:
func getParameters() -> [String: [String: AnyObject]] {
var result = [String: [String: AnyObject]]()
let mmaps = self.mmaps.allObjects as [Mmap]
for i in 0..<mmaps.count {
let mmap = mmaps[i]
let material: [String: AnyObject] = [
"material_id": mmap.material.id,
"quantity": mmap.qty
]
result["\(i)"] = material
}
return result
}
A couple of thoughts:
It would be easiest if you sent the response as a dictionary with one key, and it will correctly encode the array within the dictionary:
let materials = [ "materials":
[
[
"material_id": 1,
"qty": 10
],
[
"material_id": 2,
"qty": 5
]
]
]
You could then just supply that as the parameters
of request()
, and Alamofire will properly encode that for you.
If you wanted to send an array of dictionaries, an alternative would be to change the web service to accept JSON. You could then encode the JSON yourself (using JSONSerialization
or JSONEncoder
), set the body of the request, and then send that request.
If you want to send application/x-www-form-urlencoded
request with the array of dictionaries, you'd have to encode that yourself. In Swift 3 and later, that might look like:
func encodeParameters(_ object: Any, prefix: String? = nil) -> String {
if let dictionary = object as? [String: Any] {
return dictionary.map { key, value -> String in
self.encodeParameters(value, prefix: prefix != nil ? "\(prefix!)[\(key)]" : key)
}.joined(separator: "&")
} else if let array = object as? [Any] {
return array.enumerated().map { (index, value) -> String in
return self.encodeParameters(value, prefix: prefix != nil ? "\(prefix!)[\(index)]" : "\(index)")
}.joined(separator: "&")
} else {
let escapedValue = "\(object)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
return prefix != nil ? "\(prefix!)=\(escapedValue)" : "\(escapedValue)"
}
}
Where
extension CharacterSet {
/// Returns the character set for characters allowed in the individual parameters within a query URL component.
///
/// The query component of a URL is the component immediately following a question mark (?).
/// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query
/// component is `key1=value1`. The individual parameters of that query would be the key `key1`
/// and its associated value `value1`.
///
/// According to RFC 3986, the set of unreserved characters includes
///
/// `ALPHA / DIGIT / "-" / "." / "_" / "~"`
///
/// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters
/// for the sake of compatibility with some erroneous implementations, so this routine also allows those
/// to pass unescaped.
static var urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
Obviously, use whatever response
method is appropriate for the nature of your server's response (e.g. response
vs. responseJSON
vs. ...).
Anyway, the above generates a request body that looks like:
materials[0][material_id]=1&materials[0][qty]=10&materials[1][material_id]=2&materials[1][qty]=5
And this appears to be parsed by servers as you requested in your question.
It's worth noting that this final point illustrates the preparation of an application/x-www-form-urlencoded
request with nested dictionary/array structure, as contemplated here. This works on my server run by a major ISP, but I must confess that I haven't seen this convention documented in formal RFCs, so I'd be wary of doing it. I'd personally be inclined to implement this as JSON interface.
For prior versions of Swift, see previous revision of this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With