> Как реализовать поддержку кодирования и декодирования JSON без классов в Swift (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Revolut

Стек: iOS, Swift

> Пример ответа

Короткий ответ

В Swift для JSON без классов используют Codable с struct и enum, а не классы. Для динамических или неизвестных схем применяют JSONSerialization, AnyCodable или ручной обход [String: Any]. На senior-уровне важно учитывать trade-off между типобезопасностью, производительностью и гибкостью. Классы не обязательны, если нет reference-семантики, наследования или identity.

Подробное объяснение

Классы в Swift не являются обязательным условием для работы с JSON. Протокол Codable объединяет Encodable и Decodable и отлично работает со struct, enum и actor. Для JSON без классов обычно выбирают один из трёх подходов.

Первый подход - статические struct-модели с Codable. Это самый типобезопасный и производительный вариант. Компилятор генерирует init(from:) и encode(to:), а JSONDecoder/JSONEncoder работают с value-типами без overhead reference counting. Для разных ключей используют CodingKeys, для вложенности - вложенные struct, для вариантов - enum с ассоциированными значениями.

Второй подход - динамический JSON через JSONSerialization. Он возвращает Any, обычно [String: Any] или [Any]. Это удобно для неизвестной схемы, частичного парсинга или проксирования, но теряется типобезопасность, появляются force cast и ручные проверки. На senior-уровне такой подход применяют только там, где схема действительно не фиксирована.

Третий подход - обёртки вроде AnyCodable или собственный JSONValue enum. Он сохраняет Codable-совместимость и даёт контроль над динамическими значениями. Внутри обычно enum JSONValue { case string(String), number(Double), bool(Bool), object([String: JSONValue]), array([JSONValue]), null }. Это компромисс между типобезопасностью и гибкостью.

Для JSON без классов также важно помнить о CodingUserInfoKey, keyDecodingStrategy, dateDecodingStrategy и dataDecodingStrategy. Они позволяют адаптировать декодирование без создания классов. Если нужна reference-семантика, например разделяемое состояние или identity, тогда класс или actor оправданы, но это уже не требование JSON, а требование модели.

На практике

На практике в iOS чаще всего используют struct-модели с Codable. Для API с фиксированной схемой это даёт compile-time проверки и удобную работу с Result и async/await. Если сервер возвращает полиморфные объекты, применяют enum с Codable и кастомный init(from:). Если нужно распарсить только часть ответа, можно объявить struct только с нужными полями, а лишние ключи JSONDecoder проигнорирует.

Для динамических данных, например конфигов или feature flags, удобен JSONValue enum. Он позволяет рекурсивно обходить JSON и не использовать Any. Для отладки и логирования иногда достаточно JSONSerialization, но в production-коде лучше избегать Any и force cast.

Если модель должна быть классом, например для ObservableObject или наследования, Codable всё равно работает. Но стоит помнить, что JSONDecoder создаёт новый экземпляр, а не мутирует существующий. Для actor нужно учитывать изоляцию и Sendable.

Пример кода

SWIFT
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String?
}
enum JSONValue: Codable {
case string(String)
case number(Double)
case bool(Bool)
case object([String: JSONValue])
case array([JSONValue])
case null
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([String: JSONValue].self) {
self = .object(value)
} else if let value = try? container.decode([JSONValue].self) {
self = .array(value)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unsupported JSON value"
)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value): try container.encode(value)
case .number(let value): try container.encode(value)
case .bool(let value): try container.encode(value)
case .object(let value): try container.encode(value)
case .array(let value): try container.encode(value)
case .null: try container.encodeNil()
}
}
}
let data = #"{"id":1,"name":"Ann","email":null}"#.data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: data)
let dynamic = try JSONDecoder().decode(JSONValue.self, from: data)

Как отвечать на собеседовании

Стоит сразу сказать, что классы не нужны, если нет reference-семантики. Затем перечислить три подхода: Codable со struct, JSONSerialization и JSONValue/AnyCodable. Показать, что понимаешь trade-off: типобезопасность против гибкости, производительность против динамики. Упомянуть CodingKeys, keyDecodingStrategy, dateDecodingStrategy и обработку ошибок. Если спросят про производительность, сказать, что struct и enum обычно быстрее и безопаснее, чем Any и NSDictionary. В конце добавить, что выбор зависит от схемы JSON и требований к модели.

Что проверяет интервьюер

Интервьюер проверяет понимание Codable, value-семантики и reference-семантики. Также важно, умеет ли кандидат выбирать между статической и динамической схемой. Проверяется знание JSONSerialization, JSONDecoder, JSONEncoder и стратегий декодирования. На senior-уровне ожидается осознание trade-off, обработка ошибок, работа с вложенными и полиморфными данными. Дополнительно смотрят на аккуратность кода и отсутствие лишних Any и force cast.

Типичные ошибки

Часто кандидаты считают, что Codable работает только с классами. Это неверно, struct и enum поддерживаются полностью. Другая ошибка - использовать JSONSerialization и Any там, где схема известна. Это приводит к runtime-ошибкам и плохой читаемости. Ещё одна ошибка - игнорировать CodingKeys и стратегии декодирования, из-за чего ломается парсинг дат, snake_case и вложенных ключей. Также опасно делать force cast из Any и не обрабатывать DecodingError. Наконец, не стоит делать модель классом только ради Codable, если нет реальной необходимости в reference-семантике.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью