Swift
Alamofire
JSON Parsing
iOS
API Integration

How to parse JSON response from Alamofire API in Swift?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

The most reliable way to parse JSON from Alamofire in modern Swift is to decode directly into Codable models. That gives you type safety, clearer errors, and much less manual casting than older approaches based on [String: Any].

Model the JSON First

Before writing networking code, describe the response shape with Swift types. Suppose the API returns:

json
1{
2  "id": 42,
3  "name": "Ava",
4  "email": "[email protected]",
5  "created_at": "2026-03-04T10:00:00Z"
6}

The matching Swift model looks like this:

swift
1import Foundation
2
3struct User: Codable {
4    let id: Int
5    let name: String
6    let email: String
7    let createdAt: Date
8
9    enum CodingKeys: String, CodingKey {
10        case id
11        case name
12        case email
13        case createdAt = "created_at"
14    }
15}

This keeps JSON naming and Swift naming cleanly separated.

Decode With responseDecodable

Alamofire already integrates well with Codable, so you do not need to parse the JSON manually.

swift
1import Alamofire
2
3func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
4    AF.request("https://api.example.com/user/42")
5        .validate(statusCode: 200..<300)
6        .responseDecodable(of: User.self) { response in
7            switch response.result {
8            case .success(let user):
9                completion(.success(user))
10            case .failure(let error):
11                completion(.failure(error))
12            }
13        }
14}

This is the usual production baseline: request, validate, decode, and return a typed result.

Configure the Decoder When the API Needs It

Many APIs need a custom JSONDecoder, especially for dates or snake-case keys.

swift
1import Alamofire
2import Foundation
3
4func fetchUserWithDecoder(completion: @escaping (Result<User, Error>) -> Void) {
5    let decoder = JSONDecoder()
6    decoder.dateDecodingStrategy = .iso8601
7
8    AF.request("https://api.example.com/user/42")
9        .validate()
10        .responseDecodable(of: User.self, decoder: decoder) { response in
11            completion(response.result.mapError { $0 })
12        }
13}

If most server keys are snake case, you can often simplify models with:

swift
decoder.keyDecodingStrategy = .convertFromSnakeCase

That removes a lot of repetitive CodingKeys declarations.

Parse Nested JSON With Wrapper Models

Real APIs often wrap the payload inside another object:

json
1{
2  "data": {
3    "id": 42,
4    "name": "Ava",
5    "email": "[email protected]",
6    "created_at": "2026-03-04T10:00:00Z"
7  }
8}

Decode that by introducing a wrapper model:

swift
struct UserEnvelope: Codable {
    let data: User
}

Then request it normally:

swift
1AF.request("https://api.example.com/user/42")
2    .validate()
3    .responseDecodable(of: UserEnvelope.self) { response in
4        if case .success(let envelope) = response.result {
5            print(envelope.data.name)
6        }
7    }

Trying to unwrap nested JSON manually is rarely necessary anymore.

Distinguish Transport Errors From Decoding Errors

Not every failure means the same thing. Sometimes the network request failed. Sometimes the server returned a bad status. Sometimes the JSON structure changed and decoding failed.

swift
1AF.request("https://api.example.com/user/42")
2    .validate()
3    .responseDecodable(of: User.self) { response in
4        if let error = response.error {
5            if error.isResponseValidationError {
6                print("Status code or validation problem")
7            } else if error.isResponseSerializationError {
8                print("JSON decoding problem")
9            } else {
10                print("Transport or other request problem")
11            }
12            return
13        }
14
15        print(response.value as Any)
16    }

That kind of classification is useful when debugging production incidents and API contract drift.

Async/Await Works Nicely Too

If your app uses Swift concurrency, Alamofire can fit that style cleanly.

swift
1import Alamofire
2
3func fetchUserAsync() async throws -> User {
4    let decoder = JSONDecoder()
5    decoder.dateDecodingStrategy = .iso8601
6
7    return try await AF.request("https://api.example.com/user/42")
8        .validate()
9        .serializingDecodable(User.self, decoder: decoder)
10        .value
11}

This makes call sites simpler and keeps networking code aligned with modern Swift app architecture.

Common Pitfalls

The most common mistake is decoding into the wrong model shape. If the server returns a wrapper object and your code decodes the inner model directly, decoding will fail even though the network request succeeded.

Another issue is manual dictionary parsing with [String: Any] long after Codable would have been simpler and safer. Manual casting creates more runtime failure points.

Date parsing is another frequent source of bugs. If the API format does not match the decoder strategy, the request may fail with what looks like a mysterious serialization error.

Finally, always validate the response before assuming the body shape is correct. An error payload from the server often looks nothing like the success model.

Summary

  • Use Codable models and Alamofire responseDecodable for type-safe JSON parsing.
  • Configure JSONDecoder when the API uses custom date or key formats.
  • Add wrapper models for nested payloads instead of manual dictionary walking.
  • Separate transport, validation, and decoding failures when handling errors.
  • Prefer typed decoding over [String: Any] casting in modern Swift code.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.