Exploring Swift’s Codable Protocol: Techniques for Modifying Key Names
The Codable protocol in Swift is a powerful feature that enables the seamless transformation of data structures into external representations and vice versa. It provides a mechanism for encoding and decoding values to and from Data instances, facilitating the serialization and deserialization of data. Complementing this functionality, the Swift compiler offers an exceptional capability: it can automatically generate the necessary code to conform to the Codable protocol for struct types, provided they meet the required criteria. This compiler-generated code alleviates the burden of manual implementation, enhancing developer productivity and reducing the potential for errors. By leveraging the Codable protocol and its compiler support, developers can streamline the process of data exchange, ensuring consistent and efficient data handling across various components of their applications.
While the Codable protocol and its compiler support provide a robust and efficient solution for data encoding and decoding, there are instances where developers may need to implement the required methods manually. Such scenarios typically arise when handling JSON responses from APIs that adhere to different naming conventions or data structuring approaches. Swift’s design accommodates these situations, offering developers the flexibility to customize the behavior of Codable types as needed. This article will explore the techniques and best practices for tailoring Swift’s Codable types to align with specific requirements, ensuring seamless integration with external data sources and facilitating efficient data transformation within applications.
In general, it is possible to modify key names, values, and data structures during the encoding and decoding processes. This article will primarily concentrate on the techniques for altering key names, while the remaining aspects, namely value and data structure manipulation, will be explored in subsequent articles.
Without any customisation
Let us take the following struct as an example:
struct Person: Codable {
let firstName: String
let lastName: String
}
do {
let person = Person(firstName: "Tim", lastName: "Wang")
let data = try JSONEncoder().encode(person)
let newPerson = try JSONDecoder().decode(Person.self, from: data)
} catch {
print(error)
}
The conformance of the Codable protocol facilitates the automatic generation of the requisite encode and decode methods for the given struct by the compiler. Consequently, we can leverage the JSONEncoder and JSONDecoder classes to encode instances of the Person struct into data representations, or decode data back into Person instances, respectively.
Upon decoding the data into its JSON string representation, as illustrated below, it becomes evident that the key names employed are “firstName” and “lastName”.
{"lastName":"Wang","firstName":"Tim"}
What if we want to use SnakeCase or some other key values?
Use keyEncodingStrategy and keyDecodingStrategy
In the following example, we leverage the convertToSnakeCase and convertFromSnakeCase strategies during the encoding and decoding processes, respectively. Consequently, the JSON string representation employs the snake_case key names first_name and last_name.
do {
let person = Person(firstName: "Tim", lastName: "Wang")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(person)
// JSON String: {"last_name":"Wang","first_name":"Tim"}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let newPerson = try decoder.decode(Person.self, from: data)
} catch {
print(error)
}
The strategies are applied to all key names. However, if the requirement is to handle specific key names, the .custom strategy can be utilized, as demonstrated in the following example. In this case, only first_name key name is using SnakeCase.
enum PersonCodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName
}
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .custom({ codingPath in
switch codingPath.last?.stringValue {
case "firstName": PersonCodingKeys.firstName
default: PersonCodingKeys.lastName
}
})
let data = try encoder.encode(person)
// {"first_name":"Tim","lastName":"Wang"}
Please note, we cannot simply use .custom for keyDecodingStrategy like below. In order to make it work we need to define coding keys inside Person struct.
// NOT WORK!!!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
return switch codingPath.last?.stringValue {
case "first_name": PersonCodingKeys.firstName
default: PersonCodingKeys.lastName
}
})
let newPerson = try? decoder.decode(Person.self, from: data)
print(newPerson)
While the .custom strategy provides flexibility, its implementation can be relatively verbose. Indeed, the following alternative method can achieve the same result in a more concise manner.
Use CodingKeys definition
struct Person: Codable {
let firstName: String
let lastName: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
}
}
do {
let person = Person(firstName: "Tim", lastName: "Wang")
let data = try JSONEncoder().encode(person)
let newPerson = try JSONDecoder().decode(Person.self, from: data)
} catch {
print(error)
}
In this code snippet, we define CodingKeys within the Person struct, which will be automatically utilized by the encode and decode methods. We possess the capability to modify the values of these keys (e.g., snake_case, a combination of snake_case and non-snake_case keys, etc.) to suit our specific requirements, thereby providing maximum flexibility.
Conclusion
We have investigated various techniques for modifying key names when conforming to the Codable protocol. In scenarios that a well-defined snake_case key name mapping exists, the snake_case strategy presents the most straightforward approach. For other cases, defining CodingKeys within the struct may be a more suitable choice. However, there may also be situations where the .custom strategy is needed. For instance, when the struct type is defined within an external library, and the source code cannot be modified.
Ultimately, these methods provide a comprehensive set of tools, enabling us to handle JSON data with ease and flexibility.
Please continue to follow along, as we will delve into the customization of values and data structures when utilizing Swift’s Codable protocol in upcoming articles.
Sharing is caring!