Swift. Serialization of request parameters
Surely, every developer who needed to program the network layer of the application solved the problem of passing request parameters. In most cases, this is a simple task that can be achieved using standard tools provided by the native framework or programming language. But if we consider the situation in the context of the iOS platform and the Swift programming language, it will immediately become clear that the compiler throws an error when trying to serialize parameters as a [String: Any] dictionary. However, thanks to the innovations that appeared in iOS 15.4 and Swift 5.6, this dictionary is much more easy to serialize.
The goal
- In the case of passing parameters to the body of the request, the ability of declaring in the form of a [String: Any] dictionary is required.
let requestParameters = [
"method": "createUser",
"credentials": [
"login": login,
"password": password,
"age": age,
"notificationSettings": [
"notifyNews": isNotifyNews,
"notifyCabinet": isCabinetNotify
]
]
]
With this approach, it becomes much easier to maintain the project, as it eliminates the creation of "trash" models.
struct LoginRequest: Codable {
struct Credentials: Codable {
struct NotificationSettings: Codable {
var notifyNews: Bool
var notifyCabinet: Bool
}
var login: String
var password: String
var age: Int
var notificationSettings: NotificationSettings
}
var method: String
var credentials: Credentials
}
We will immediately make a reservation that it is not worth passing parameters such as login and password in a network request, since for these purposes there is already legacy Basic authentication technology, as well as a more modern approach using access and refresh tokens.
- In the case of passing parameters as a query string, it is required that the order of elements, set during initialization won't change.
let requestParameters = [
"email": email,
"firstName": firstName,
"age": age
]email=example@example.com&firstName=Nickey&age=20
This requirement is necessary for cases when parameters are encrypted (when an encrypted hash of a given string is additionally sent).
The solution
Let’s start serializing the parameters to the body of the request. With the CodingKeyRepresentable protocol introduced in iOS 15.4 and the type erasure technique introduced in Swift 5.6, it’s easier to encode the Any type.
Suppose there is some container for parameters — Query object, which uses an array as storage in order to preserve the order of elements, but at the same time implements all the features of a dictionary.
Then, in order to use the CodingKeyRepresentable protocol, we will need an object that implements the CodingKey protocol.
As a result, to implement the Encodable protocol, it is enough to program the encode(to:) method.
Next, we will solve the problem of encoding into a query string. Firstly, there are several options for encoding an array and bool values, so we need to describe these options, for example, as corresponding enums.
Further, in order to create a query string, we should use the standard library — URLComponents and URLQueryItem. Thus, to convert each parameter into a URLQueryItem, it is enough to declare the corresponding method.
An attentive reader may ask why the constraint is added?
where Key == String
This limitation is due to the type (String) of the first field of the URLQueryItem structure.
Use
In the first case, when it is required to pass parameters to the body of the request, the method for creating and executing the request will be as follows
In the second case, to create a query string, the request is presented below
Conclusion
Previously, before the advent of CodingKeyRepresentable and type erasure, this solution could also be programmed, only for this it was necessary to additionally create an AnyEncodable container and check the key field for compliance with the String or Int type. However, with the evolution of the platform, it became much more convenient to work with the dictionary, and developer can finally forget about the new trash request model.