Introduction
In iOS development, working with JSON data is a common task. To simplify the process of decoding JSON into Swift objects, Apple introduced the Decodable protocol. Decodable is a powerful protocol that automatically maps JSON data to Swift types, making JSON decoding straightforward. However, there are scenarios where the JSON structure may not precisely match the desired Swift type, leading to decoding failures. In this article, we’ll explore how to gracefully handle such edge cases using a neat DecodableData
extension.
Handling JSON Mismatch
Consider a scenario where we have JSON representing user data like this:
1
2
3
4
5
6
7
{
"user_data": {
"full_name": "General Iroh",
"country": "Fire Nation",
"occupation": "Professional Tea Enjoyer"
}
}
However, we want to map this JSON to a Swift type that looks like this:
1
2
3
4
5
6
struct User: Decodable {
var name: String
var country: String
var occupation: String
var hobbies: [String]
}
The problem here is that we have an additional property called "hobbies"
that doesn’t exist in the incoming JSON, and the "name"
property doesn’t match the JSON key "full_name"
. Fortunately, there is a clean extension we can improvise to handle this mismatch gracefully.
The DecodableData Extension
To address the JSON-Swift type mismatch, we can define an extension called DecodableData
for the User struct. Let’s take a look at the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct User {
let name: String
let country: String
let occupation: String
var hobbies: [String]
}
extension User {
struct DecodableData: Decodable {
let fullName: String
let country: String
let occupation: String
var user: User {
return User(name: fullName, country: country, occupation: occupation)
}
}
}
The DecodableData
extension provides a solution for decoding the user JSON data. It includes properties that match the JSON structure, such as “fullName”, “country”, and “occupation.” Additionally, it includes a computed property called "user"
, which returns an instance of the original User struct based on the decoded data. By mapping the properties correctly in the DecodableData
struct, we can bridge the gap between the JSON structure and our desired Swift type.
Using DecodableData
To make use of DecodableData
in a decoder, follow these steps:
1
2
3
4
5
6
7
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// Decode an instance of User.DecodableData instead of User directly,
// then convert it to a User instance.
let decodableData = try decoder.decode(User.DecodableData.self, from: data)
let user = decodableData.user
In the above code, we use the "convertFromSnakeCase"
key decoding strategy to automatically convert the "full_name"
snake_case key from the JSON data into its "fullName"
camelCase equivalent, matching our Swift code.
Conclusion
As you can see, the DecodableData
extension proves invaluable when dealing with JSON-Swift type mismatches. By utilizing this approach, we can decode or encode specific types even when the original type has a completely different structure than the JSON data. This technique allows us to bridge the gap between serialized data and our Swift code, while still leveraging the power of auto mapping that the Decodable feature provides us.