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

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

Компании: Лингуа Лео

Стек: iOS, Swift

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

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

Основная стратегия - не привязывать модель данных напрямую к JSON. Используем промежуточные DTO (Data Transfer Object) с Codable, затем маппим в доменные модели. Для несовпадающих полей применяем CodingKeys для переименования, кастомный init(from:) для трансформаций, а для опциональных или отсутствующих ключей - decodeIfPresent и значения по умолчанию. Если структура JSON сильно отличается от модели, добавляем слой маппинга с явной логикой преобразования.

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

Проблема возникает, когда сервер отдаёт поля с другими именами, типами, вложенностью или когда часть данных отсутствует. Прямое использование Codable с struct не всегда работает из-за строгой типизации Swift.

Основные инструменты:

  • CodingKeys - переименование ключей, если имена в JSON отличаются от свойств модели.
  • init(from decoder:) - кастомная логика декодирования: преобразование типов, объединение полей, обработка отсутствующих значений.
  • decodeIfPresent - для опциональных полей, чтобы не падать при отсутствии ключа.
  • Промежуточные DTO - когда JSON сложный или нестабильный, создаём структуры, повторяющие JSON, затем маппим в доменные модели.
  • JSONDecoder настройки: keyDecodingStrategy = .convertFromSnakeCase для snake_case, dateDecodingStrategy для дат.

Для полей, которые не совпадают по типу (например, строка вместо числа), используем кастомный init(from:) с ручным приведением. Если поле может отсутствовать - decodeIfPresent или ?? со значением по умолчанию.

На практике

На уровне senior важно не просто заставить код работать, а выбрать правильный уровень абстракции. Если JSON меняется часто - DTO обязателен. Если изменения редки и локальны - достаточно CodingKeys и кастомного инициализатора.

Также стоит учитывать: не нужно хранить сырые JSON-поля в доменной модели. Доменная модель должна быть чистой, без зависимости от формата передачи. Это упрощает тестирование и замену API.

Для больших проектов полезно выносить маппинг в отдельные extension или фабрики, чтобы не раздувать модели.

Пример кода

SWIFT
// JSON: {"user_name": "John", "age_str": "30", "address": {"city": "Moscow"}}
struct UserDTO: Decodable {
let userName: String
let ageStr: String
let address: AddressDTO
enum CodingKeys: String, CodingKey {
case userName = "user_name"
case ageStr = "age_str"
case address
}
}
struct AddressDTO: Decodable {
let city: String
}
// Доменная модель
struct User {
let name: String
let age: Int
let city: String
}
extension User {
init(from dto: UserDTO) {
self.name = dto.userName
self.age = Int(dto.ageStr) ?? 0
self.city = dto.address.city
}
}
// Использование
let decoder = JSONDecoder()
let dto = try decoder.decode(UserDTO.self, from: data)
let user = User(from: dto)

Пример с кастомным декодированием для нестандартного типа:

SWIFT
struct Product: Decodable {
let id: Int
let price: Decimal
enum CodingKeys: String, CodingKey {
case id, price
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
// JSON отдаёт цену строкой
let priceString = try container.decode(String.self, forKey: .price)
guard let value = Decimal(string: priceString) else {
throw DecodingError.dataCorruptedError(
forKey: .price,
in: container,
debugDescription: "Invalid price format"
)
}
price = value
}
}

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

Начни с краткого ответа, затем переходи к деталям. Покажи, что понимаешь разницу между DTO и доменной моделью. Упомяни CodingKeys, decodeIfPresent, кастомный init(from:). Приведи пример из практики, где поля не совпадали, и объясни, почему выбрал тот или иной подход. Если спросят про производительность - отметь, что декодирование происходит один раз, а маппинг в доменные модели обычно дёшев.

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

  • Понимание Codable и его ограничений.
  • Умение проектировать слои данных.
  • Навыки работы с опциональными и нестандартными полями.
  • Способность объяснить trade-off между простотой и гибкостью.
  • Внимание к деталям: обработка ошибок, значения по умолчанию.

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

  • Использование try? без обработки ошибок - теряется диагностика.
  • Привязка доменной модели к JSON-структуре напрямую.
  • Игнорирование decodeIfPresent - падение при отсутствии ключа.
  • Хранение сырых строк вместо преобразования типов в init(from:).
  • Слишком сложный маппинг там, где достаточно CodingKeys.
  • Забывают про keyDecodingStrategy для snake_case.

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

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