> Как реализовать поддержку кодирования и декодирования 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.
Пример кода
SWIFTimport Foundationstruct User: Codable {let id: Intlet name: Stringlet email: String?}enum JSONValue: Codable {case string(String)case number(Double)case bool(Bool)case object([String: JSONValue])case array([JSONValue])case nullinit(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-семантике.
> Похожие задачи по mobile
Какой опыт работы с фреймами и автолейаутом в iOS?
Какой опыт с Objective-C
Может ли dataCompanies быть nil при декодировании JSON в Swift?
Как посчитать количество уникальных значений в колонке DataFrame?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью