DEV Community

Anis Ali Khan
Anis Ali Khan

Posted on

Tutorial 16: Parsing JSON with Codable and Decodable in Swift

Introduction

JSON (JavaScript Object Notation) is a lightweight data-interchange format widely used in APIs and web services. Swift provides the Codable protocol, which makes it easy to decode JSON into Swift structures and encode Swift objects back to JSON.

In this tutorial, we will build a simple Weather App that fetches real-time weather data using URLSession, parses the JSON response using Codable, and displays the weather information in a SwiftUI interface. By the end, you will understand how to work with JSON in Swift and apply best practices for error handling and asynchronous API calls.


Understanding Codable and Decodable

Swift provides two protocols:

  • Decodable: Allows decoding JSON into Swift objects.
  • Encodable: Allows encoding Swift objects into JSON.
  • Codable: A typealias for both Encodable and Decodable.

Example of a JSON response for Weather Data

Here’s an example JSON response from a weather API:

{
    "location": {
        "name": "New York",
        "region": "New York",
        "country": "USA"
    },
    "current": {
        "temperature": 22,
        "condition": {
            "text": "Sunny",
            "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We will map this JSON response to Swift structs using Codable.


Step 1: Define the Data Models

To parse the JSON, we define Swift structures that conform to Codable.

struct WeatherResponse: Codable {
    let location: Location
    let current: CurrentWeather
}

struct Location: Codable {
    let name: String
    let region: String
    let country: String
}

struct CurrentWeather: Codable {
    let temperature: Int
    let condition: WeatherCondition
}

struct WeatherCondition: Codable {
    let text: String
    let icon: String
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Fetch Weather Data from API

We will use URLSession to make a network request to fetch the weather data.

import Foundation

class WeatherService {
    func fetchWeather(for city: String, completion: @escaping (WeatherResponse?) -> Void) {
        let urlString = "https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=\(city)"
        guard let url = URL(string: urlString) else { return }

        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                completion(nil)
                return
            }

            do {
                let weather = try JSONDecoder().decode(WeatherResponse.self, from: data)
                DispatchQueue.main.async {
                    completion(weather)
                }
            } catch {
                print("Failed to decode JSON: \(error)")
                completion(nil)
            }
        }.resume()
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Build a SwiftUI Weather App

Now, we will use SwiftUI to display the weather data.

import SwiftUI

struct ContentView: View {
    @State private var weather: WeatherResponse?
    private let weatherService = WeatherService()

    var body: some View {
        VStack {
            if let weather = weather {
                Text(weather.location.name)
                    .font(.largeTitle)
                Text("\(weather.current.temperature)°C")
                    .font(.system(size: 50))
                Text(weather.current.condition.text)
                    .font(.title2)
                AsyncImage(url: URL(string: "https:\(weather.current.condition.icon)"))
            } else {
                Text("Fetching Weather...")
                    .onAppear {
                        weatherService.fetchWeather(for: "New York") { response in
                            self.weather = response
                        }
                    }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Handling Errors Gracefully

To improve the user experience, let's handle errors properly and provide UI feedback.

enum WeatherError: Error {
    case invalidURL, requestFailed, decodingFailed
}

class ImprovedWeatherService {
    func fetchWeather(for city: String, completion: @escaping (Result<WeatherResponse, WeatherError>) -> Void) {
        let urlString = "https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=\(city)"
        guard let url = URL(string: urlString) else {
            completion(.failure(.invalidURL))
            return
        }

        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                completion(.failure(.requestFailed))
                return
            }

            do {
                let weather = try JSONDecoder().decode(WeatherResponse.self, from: data)
                DispatchQueue.main.async {
                    completion(.success(weather))
                }
            } catch {
                completion(.failure(.decodingFailed))
            }
        }.resume()
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Enhance UI with Loading State and Error Messages

struct EnhancedWeatherView: View {
    @State private var weather: WeatherResponse?
    @State private var errorMessage: String?
    private let weatherService = ImprovedWeatherService()

    var body: some View {
        VStack {
            if let weather = weather {
                Text(weather.location.name)
                    .font(.largeTitle)
                Text("\(weather.current.temperature)°C")
                    .font(.system(size: 50))
                Text(weather.current.condition.text)
                    .font(.title2)
                AsyncImage(url: URL(string: "https:\(weather.current.condition.icon)"))
            } else if let errorMessage = errorMessage {
                Text(errorMessage)
                    .foregroundColor(.red)
                    .padding()
            } else {
                ProgressView("Fetching Weather...")
                    .onAppear {
                        weatherService.fetchWeather(for: "New York") { result in
                            switch result {
                            case .success(let weatherData):
                                self.weather = weatherData
                            case .failure(let error):
                                self.errorMessage = "Error: \(error)"
                            }
                        }
                    }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this tutorial, we covered:

  • Understanding Codable and Decodable
  • Fetching JSON data using URLSession
  • Parsing JSON into Swift structures
  • Handling errors gracefully
  • Displaying data in a SwiftUI interface

With these skills, you can work with any JSON-based API and build powerful iOS apps. 🚀

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (1)

Collapse
 
ccc_m_bfdfc93e9bbdfc53fbb profile image
ccc M

You can try using SmartCodable for a better experience than Codable.

github.com/intsig171/SmartCodable

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay