Working with JSON in Swift: Parsing and Serialization in Practice
JSON (JavaScript Object Notation) is a lightweight data interchange format that has become the de facto standard for client-server communication in mobile and web development. In the Apple ecosystem (iOS, macOS, watchOS, tvOS), working with JSON is done through powerful built-in mechanisms. In this article, we will thoroughly examine how to parse and serialize JSON in Swift using real examples, employing both the modern Codable protocol and the classic JSONSerialization.
This article is intended for developers who are already familiar with the basics of Swift and want to confidently work with network requests and local data storage in JSON format.
1. JSON Basics and Its Place in Swift Applications
JSON is a text format based on two structures: objects (sets of key-value pairs) and arrays (ordered lists). In Swift, these structures correspond to Dictionary and Array. Data types in JSON are limited: strings, numbers, boolean values, null, objects, and arrays.
A typical scenario for using JSON in an iOS/macOS application looks like this:
- Receiving a JSON string from the server (via URLSession).
- Parsing JSON into Swift models (e.g., structs or classes).
- Processing the data and displaying it in the interface.
- Serializing (reverse conversion) models into JSON for sending to the server.
Apple provides two main approaches for working with JSON: JSONSerialization (low-level but flexible) and Codable (high-level, type-safe, recommended since Swift 4+).
2. Parsing JSON with Codable (Recommended Way)
The Codable protocol combines Encodable (for serialization) and Decodable (for parsing). This is the simplest and safest way to work with JSON in Swift.
Example 1: Parsing a Simple JSON Object
Suppose the server returns the following JSON:
{ "userId": 1, "id": 1, "title": "Example task", "completed": false}Let's create a model struct conforming to Codable:
struct TodoItem: Codable { let userId: Int let id: Int let title: String let completed: Bool}Now let's parse the JSON string:
let jsonString = """{ "userId": 1, "id": 1, "title": "Example task", "completed": false}"""
if let jsonData = jsonString.data(using: .utf8) { let decoder = JSONDecoder() do { let todo = try decoder.decode(TodoItem.self, from: jsonData) print("Task: \\(todo.title), completed: \\(todo.completed)") } catch { print("Parsing error: \\(error.localizedDescription)") }}Example 2: Parsing an Array of Objects
If the server returns an array of tasks:
[ {"userId": 1, "id": 1, "title": "Task 1", "completed": false}, {"userId": 1, "id": 2, "title": "Task 2", "completed": true}]Parsing is just as simple — you need to specify the array type:
let todoArray = try decoder.decode([TodoItem].self, from: jsonData)2.1. Custom Keys and snake_case
Often JSON uses snake_case format (e.g., user_id), while Swift uses camelCase (userId). JSONDecoder can convert keys automatically:
decoder.keyDecodingStrategy = .convertFromSnakeCaseIf you need to specify a non-standard mapping, use the CodingKeys enum:
struct User: Codable { let fullName: String let emailAddress: String
enum CodingKeys: