Что такое Codable в Swift и как настраивать кодирование/декодирование?
Codable = Encodable & Decodable. Swift генерирует реализацию автоматически; для кастомизации используют CodingKeys, init(from:) и encode(to:). JSONDecoder/JSONEncoder — основные точки входа.
Что такое Codable
Codable — это псевдоним (typealias) для пары протоколов Encodable & Decodable, введённых в Swift 4. Компилятор автоматически синтезирует соответствие, если все хранимые свойства сами являются Codable. Это позволяет сериализовать структуры и классы в JSON, Plist или любой другой формат без написания boilerplate-кода.
Базовый пример
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
}
// Декодирование из JSON
let json = """
{
"id": 42,
"name": "Alice",
"email": "alice@example.com"
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json)
print(user.name) // Alice
// Кодирование обратно в JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try encoder.encode(user)
print(String(data: encoded, encoding: .utf8)!)
Кастомизация через CodingKeys
Если имена свойств Swift не совпадают с ключами JSON (например, сервер возвращает snake_case), используют вложенный enum CodingKeys:
struct Article: Codable {
let id: Int
let title: String
let publishedAt: Date
let authorName: String
enum CodingKeys: String, CodingKey {
case id
case title
case publishedAt = "published_at"
case authorName = "author_name"
}
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// Теперь published_at -> publishedAt автоматически
Альтернатива — задать decoder.keyDecodingStrategy = .convertFromSnakeCase, тогда CodingKeys вообще не нужен, если все имена укладываются в конвенцию.
Ручная реализация init(from:) и encode(to:)
Когда нужна нетривиальная логика (объединить два поля, вычислить значение, обработать полиморфизм), реализуют протоколы вручную:
struct Point: Codable {
var x: Double
var y: Double
// JSON выглядит как [1.0, 2.0] — массив, не объект
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
x = try container.decode(Double.self)
y = try container.decode(Double.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(x)
try container.encode(y)
}
}
let data = "[3.5, 7.2]".data(using: .utf8)!
let point = try JSONDecoder().decode(Point.self, from: data)
print(point.x, point.y) // 3.5 7.2
Полиморфизм и вложенные контейнеры
Реальные API часто возвращают тип-дискриминатор. Стандартный паттерн:
enum Shape: Codable {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
private enum CodingKeys: String, CodingKey { case type, radius, width, height }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let type = try c.decode(String.self, forKey: .type)
switch type {
case "circle":
let r = try c.decode(Double.self, forKey: .radius)
self = .circle(radius: r)
case "rectangle":
let w = try c.decode(Double.self, forKey: .width)
let h = try c.decode(Double.self, forKey: .height)
self = .rectangle(width: w, height: h)
default:
throw DecodingError.dataCorruptedError(
forKey: .type, in: c,
debugDescription: "Unknown shape: \(type)"
)
}
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .circle(let r):
try c.encode("circle", forKey: .type)
try c.encode(r, forKey: .radius)
case .rectangle(let w, let h):
try c.encode("rectangle", forKey: .type)
try c.encode(w, forKey: .width)
try c.encode(h, forKey: .height)
}
}
}
Настройки JSONDecoder и JSONEncoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
decoder.dataDecodingStrategy = .base64
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Подводные камни
- Отсутствующий ключ vs null.
decode(_:forKey:)бросает ошибку, если ключ отсутствует. Для опциональных полей используйтеdecodeIfPresent, иначе весь объект провалится сkeyNotFound. - Смешение стратегий ключей. Если задать
.convertFromSnakeCaseи одновременно написатьCodingKeysс явными строками, стратегия применяется поверх маппинга — поведение становится непредсказуемым. - Дата без стратегии. По умолчанию
JSONDecoderожидаетDouble(Unix timestamp). ЗабытыйdateDecodingStrategy = .iso8601даётtypeMismatchв рантайме, а не на этапе компиляции. - Enum с ассоциированными значениями. Swift не синтезирует
Codableдля enum с ассоциированными значениями автоматически (до Swift 5.5 включительно) — нужна ручная реализация или сторонние библиотеки. - Производительность на больших данных.
JSONDecoderстроит промежуточное дерево (JSONValue) перед маппингом. На массивах из сотен тысяч объектов это заметно; рассмотрите потоковую обработку илиCodable+ CoreData batch insert. - Скрытая потеря данных. Если сервер добавил новое поле, а в модели его нет, оно молча игнорируется. Нет механизма «strict» декодирования из коробки — дополнительные ключи не вызывают ошибку.
- Circular references.
Codableне умеет в циклические графы объектов — получите бесконечную рекурсию или переполнение стека. Нужно разрывать цикл вручную. - Синтез не работает с
@propertyWrapperбез поддержки Codable. Если обёртка не реализуетCodable, автосинтез ломается со сложным сообщением об ошибке, не указывающим на обёртку.
Common mistakes
- Сводить «
Codableв Swift и как настраивать кодирование/декодирование» к синтаксису и не объяснять система типов. - Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии swift-20.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «
Codableв Swift и как настраивать кодирование/декодирование» и подтверждает ее корректным примером. - Умеет связать ответ с ARC, тестированием и отладкой на устройстве.
- Называет ограничения подхода swift-20, включая производительность, память и сопровождение.