Skip to Content
Course content

38: Encoding and Decoding with Codable

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I actually turn a JSON string into a Swift object?

In the real world, you're almost always dealing with data coming from a web API. The "magic" happens through the Codable protocol, which is actually just a type alias for Encodable and Decodable. If you just want to read data, you only need Decodable, but usually, we just use Codable to keep things simple.

Let's say we're building a movie app. The API sends us a JSON object representing a film. To handle this, you create a struct that mirrors the structure of that JSON. I always recommend keeping your data models as simple structs.

struct Movie: Codable {
    let title: String
    let releaseYear: Int
    let rating: Double
}

let json = """
{
    "title": "Inception",
    "releaseYear": 2010,
    "rating": 8.8
}
""".data(using: .utf8)!

do {
    let decoder = JSONDecoder()
    let movie = try decoder.decode(Movie.self, from: json)
    print("Now we have a Swift object: \(movie.title)")
} catch {
    print("Decoding failed: \(error)")
}

Notice the Movie.self part? You're telling the decoder exactly what type of object it should be trying to create from that blob of data. Also, don't skip the do-catch block. Decoding fails constantly in production because APIs change without warning, and if you use try!, your app will just crash.

What if the JSON keys don't match my Swift naming conventions?

This is where things usually get annoying. Most APIs use snake_case (like release_year), but in Swift, we use camelCase. You don't want to pollute your clean Swift code with underscores just to satisfy a legacy API.

You have two options here. If the entire API is consistent with snake_case, you can just tell the decoder to convert it automatically. It's a lifesaver.

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// Now "release_year" in JSON automatically maps to "releaseYear" in your struct

But what if the keys are completely different? Like if the API calls it film_title but you want your property to be called name? In that case, you use a CodingKeys enum. I've used this a thousand times to map weirdly named API fields to something that actually makes sense in my project.

struct Movie: Codable {
    let name: String
    let releaseYear: Int

    enum CodingKeys: String, CodingKey {
        case name = "film_title"
        case releaseYear = "release_year"
    }
}

The enum must be named exactly CodingKeys and conform to String and CodingKey. Swift will then use those mappings during the decoding process.

How do I stop my app from crashing when a field is missing?

I can't stress this enough: never trust an API. One day the rating field is there, and the next day the backend team decides it should be optional, and suddenly your decode call throws an error and your whole screen goes blank.

The solution is simple: use optionals. If a property in your struct is optional, JSONDecoder will simply set it to nil if the key is missing or the value is null in the JSON, rather than failing the entire decoding process.

struct Movie: Codable {
    let title: String
    let rating: Double? // This won't crash if "rating" is missing
    let director: String? // Great for fields that aren't always provided
}

By making rating optional, you're telling Swift: "I'd like this value if it's there, but if it isn't, just keep moving." It's a much more resilient way to write your data layer. When you use the property later, you just handle the nil case with a default value or an if let statement.




📋 Practical Task

Exercise: Build a Weather Station Data Parser

You are receiving data from a remote weather station. Some of the sensors are occasionally offline, meaning some data points might be missing from the JSON payload. Your goal is to create a robust parser that doesn't crash when data is missing.

Requirements:

  • Create a struct named WeatherReading that conforms to Codable.
  • The JSON keys are city_name, temperature, and wind_speed.
  • Map city_name to a Swift property called city.
  • Make wind_speed optional, as the wind sensor often fails.
  • Write a small piece of code that decodes the following JSON string: "{"city_name": "Seattle", "temperature": 12.5}" (Note: wind_speed is missing!).
  • Print the city name and, if the wind speed exists, print it; otherwise, print "Wind data unavailable".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.