Работа с JSON в Swift: парсинг и сериализация
JSON (JavaScript Object Notation) — это легковесный формат обмена данными, широко используемый в веб-сервисах и мобильных приложениях. В экосистеме Apple (iOS, macOS, watchOS, tvOS) работа с JSON является неотъемлемой частью разработки: от загрузки данных с сервера до сохранения настроек приложения. В этой статье мы подробно рассмотрим, как выполнять парсинг (декодирование) и сериализацию (кодирование) JSON в Swift, используя встроенные протоколы Codable, JSONDecoder и JSONEncoder.
1. Основы Codable: модель данных
Современный подход к работе с JSON в Swift базируется на протоколе Codable, который объединяет два протокола: Encodable (для сериализации) и Decodable (для парсинга). Чтобы ваша структура или класс могли быть преобразованы в JSON и обратно, достаточно объявить их как Codable.
Рассмотрим пример модели пользователя:
struct User: Codable {
let id: Int
let name: String
let email: String
let isActive: Bool
}
Если ключи в JSON отличаются от названий свойств в Swift (например, используется snake_case вместо camelCase), вы можете использовать перечисление CodingKeys:
struct User: Codable {
let id: Int
let fullName: String
let email: String
let isActive: Bool
enum CodingKeys: String, CodingKey {
case id
case fullName = "full_name"
case email
case isActive = "is_active"
}
}
2. Парсинг JSON с помощью JSONDecoder
Для преобразования JSON-строки или данных в объекты Swift используется JSONDecoder. Основной метод — decode(_:from:), который принимает тип данных (соответствующий Decodable) и объект Data.
Пример парсинга JSON-строки, полученной от API:
let jsonString = """
{
"id": 1,
"name": "Иван Петров",
"email": "ivan@example.com",
"is_active": true
}
"""
let jsonData = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)
print("Пользователь: \(user.name), email: \(user.email)")
} catch {
print("Ошибка декодирования: \(error.localizedDescription)")
}
Часто JSON приходит в виде массива объектов. В этом случае просто укажите тип [User].self:
let jsonArrayString = """
[
{"id": 1, "name": "Иван", "email": "ivan@test.com", "is_active": true},
{"id": 2, "name": "Мария", "email": "maria@test.com", "is_active": false}
]
"""
let jsonArrayData = Data(jsonArrayString.utf8)
do {
let users = try JSONDecoder().decode([User].self, from: jsonArrayData)
users.forEach { print($0.name) }
} catch {
print("Ошибка: \(error)")
}
3. Сериализация объектов в JSON с JSONEncoder
Обратный процесс — преобразование объекта Swift в JSON — выполняется с помощью JSONEncoder. Метод encode(_:) возвращает объект Data, который затем можно преобразовать в строку или отправить на сервер.
let user = User(id: 3, fullName: "Алексей Смирнов", email: "alex@example.com", isActive: true)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // для читаемого форматирования
let jsonData = try encoder.encode(user)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
} catch {
print("Ошибка кодирования: \(error)")
}
Вывод будет выглядеть так:
{
"id" : 3,
"full_name" : "Алексей Смирнов",
"email" : "alex@example.com",
"is_active" : true
}
Обратите внимание, что JSONEncoder автоматически использует ключи из CodingKeys (если они заданы).
4. Работа с вложенными структурами и опциональными полями
Реальные JSON-данные часто содержат вложенные объекты, массивы и опциональные поля. Swift Codable легко справляется с этим.
Пример модели с вложенными данными:
struct Company: Codable {
let name: String
let employees: [Employee]
}
struct Employee: Codable {
let id: Int
let name: String
let position: String?
let salary: Double?
}
let jsonCompany = """
{
"name": "TechCorp",
"employees": [
{"id": 1, "name": "Ольга", "position": "iOS Developer", "salary": 120000},
{"id": 2, "name": "Дмитрий", "position": nil, "salary": null}
]
}
"""
let companyData = Data(jsonCompany.utf8)
do {
let company = try JSONDecoder().decode(Company.self, from: companyData)
for employee in company.employees {
let pos = employee.position ?? "Не указана"
let sal = employee.salary != nil ? "\(employee.salary!) руб." : "Не указана"
print("\(employee.name): \(pos), зарплата: \(sal)")
}
} catch {
print("Ошибка: \(error)")
}
В этом примере поля position и salary объявлены как опциональные (?), поэтому Codable корректно обработает null в JSON.
5. Обработка ошибок и кастомные стратегии
При работе с JSON важно корректно обрабатывать ошибки. JSONDecoder предоставляет несколько полезных свойств для настройки парсинга:
keyDecodingStrategy— позволяет автоматически конвертировать ключи (например, изsnake_caseвcamelCase).dateDecodingStrategy— задает формат для парсинга дат.dataDecodingStrategy— определяет, как декодировать бинарные данные.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
struct Event: Codable {
let eventName: String
let eventDate: Date
}
let jsonEvent = """
{"event_name": "WWDC", "