Десеріалізація — процес відновлення об’єкта з потоку даних JSON, XML або Protobuf, необхідний для будь-якого мобільного застосунку, що працює з віддаленим API. За даними Apple Developer (2026), неправильне оброблення вхідних даних залишається однією з частих причин падінь на пристроях. JSONDecoder на iOS і Gson на Android — стандартні інструменти, але кожен має свої особливості та обмеження.
Головне
Десеріалізація — процес перетворення потоку байтів або структурованого тексту в об’єкт мови програмування. У мобільній розробці цей процес відбувається щоразу, коли застосунок отримує відповідь від сервера: рядок JSON перетворюється на екземпляр класу User, Order або Product. Від коректності десеріалізації безпосередньо залежить стабільність екранів, що відображають дані користувачеві.
Серіалізація та десеріалізація — взаємно обернені процеси, рідко симетричні на практиці. Серіалізація перетворює об’єкт у рядок для відправлення на сервер, десеріалізація відновлює об’єкт з отриманого рядка. Сервер може надіслати поле, якого немає в моделі клієнта, використати інший формат дати або повернути null замість числа. За даними Square Engineering (2025), асиметрія форматів є причиною 23% помилок мережевого шару в Android-застосунках. Для зниження ризику застосовують версіонування схеми та строгу контрактну специфікацію через OpenAPI.
JSON залишається найпопулярнішим форматом для мобільних API завдяки людській читабельності та вбудованій підтримці. Protobuf від Google використовується в high-load системах — він у 3-6 разів компактніший за JSON і швидше парситься, але потребує генерації коду з .proto-файлів і нечитабельний без інструментів. XML рідше зустрічається в сучасних мобільних застосунках, однак застосовується в SOAP-сервісах корпоративних систем і конфігураційних файлах Android. MessagePack — бінарний формат, схожий на JSON за структурою, але компактніший, популярний у системах реального часу.
Процес десеріалізації проходить три стадії. Спочатку токенізація розбиває сирий текст на лексеми: ключі, рядки, числа та роздільники. Потім синтаксичний аналіз перевіряє коректність структури — чи закриті дужки, чи правильний тип лапок, чи відповідає формат специфікації RFC 8259. Фінальна стадія — мапінг на об’єктну модель застосунку, де кожному ключу JSON призначається властивість класу з урахуванням стратегії іменування.
У мобільній розробці склалися два підходи до мапінгу. Reflection (Gson, JSONSerialization) аналізує структуру класу в runtime через Java Reflection API або Objective-C runtime — це гнучко і не потребує додаткової конфігурації, але повільніше і споживає більше пам’яті. Code generation (Moshi codegen, kotlinx.serialization, Codable) генерує код на етапі компіляції: швидше, безпечніше за типами і не розкриває внутрішню структуру через reflection. JetBrains і Square рекомендують code generation для production-збірок — приріст продуктивності сягає 2-4 разів у бенчмарках Google.
struct User: Codable {
let id: Int
let name: String
let email: String
let createdAt: Date
}
let json = """
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"created_at": "2026-06-01T12:00:00Z"
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try decoder.decode(User.self, from: data)
Приклад десеріалізації JSON у модель User на Swift. Стратегія convertFromSnakeCase автоматично перетворює snake_case ключі API в camelCase властивості моделі — стандартна практика в iOS-проєктах. Параметр data — це сирі байти відповіді сервера, отримані через URLSession. Обробка помилок через try дозволяє перехопити некоректний JSON без крашу застосунку.
JSONDecoder підтримує чотири стратегії ключів: useDefaultKeys (точний збіг), convertFromSnakeCase (snake_case → camelCase), custom (closure) і convertFromKebabCase (kebab-case → camelCase). Для дат передбачені .iso8601, .secondsSince1970, .millisecondsSince1970 і кастомний dateFormatter. Вибір правильної стратегії — перший крок до стійкої десеріалізації, що запобігає більшості помилок невідповідності форматів.
JSONDecoder — стандартний механізм десеріалізації в iOS SDK, що працює з протоколом Codable. JSONDecoder автоматично парсить JSON в екземпляри struct або class, підтримуючи вкладені об’єкти, масиви та примітиви. Для кастомної логіки використовується метод init(from: Decoder) — він дозволяє обробити нестандартні формати, пропущені поля на старій версії API або об’єднати кілька ключів JSON в одну властивість.
struct Order: Decodable {
let orderId: String
let amount: Double
let status: OrderStatus
enum OrderStatus: String, Decodable {
case pending, confirmed, shipped, cancelled
}
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let order = try decoder.decode(Order.self, from: jsonData)
DateDecodingStrategy визначає, як JSONDecoder інтерпретує рядки з датами. Найчастіше використовується .iso8601 — стандартний формат REST API. Вкладений enum OrderStatus автоматично декодується з рядкових значень JSON. Це дозволяє уникнути магічних чисел і робить код самодокументованим — статус замовлення завжди має строго визначений набір значень.
Починаючи з Swift 4.2, Codable підтримує property wrappers для кастомної десеріалізації окремих властивостей. @DefaultValue — популярний wrapper, що задає значення за замовчуванням, якщо поле відсутнє в JSON. @LosslessString перетворює рядок у число і навпаки. Це особливо корисно, коли сервер надсилає id у вигляді рядка "123", а модель очікує Int. Property wrappers скорочують шаблонний код в init(from:) і роблять моделі чистішими.
На Android вибір бібліотеки десеріалізації залежить від мови та вимог проєкту. Gson від Google — найпоширеніший варіант, що працює через reflection, але має проблеми з продуктивністю на складних ієрархіях. Moshi від Square підтримує як reflection, так і code generation, споживаючи менше пам’яті та швидше обробляючи великі відповіді. kotlinx.serialization від JetBrains — нативне Kotlin-рішення з інтеграцією в компілятор, що не використовує reflection взагалі.
@Serializable
data class User(
@SerialName("user_id")
val userId: Int,
val name: String,
val email: String,
@SerialName("created_at")
val createdAt: String
)
val json = Json { ignoreUnknownKeys = true }
val user = json.decodeFromString<User>(response)
@Serializable — анотація Kotlin-компілятора, що активує кодогенерацію для класу. Параметр ignoreUnknownKeys запобігає крашу, якщо сервер надіслав поле, відсутнє в моделі. Для мапінгу snake_case ключів використовується @SerialName — аналог convertFromSnakeCase з iOS. За даними JetBrains (2026), бібліотека підтримує мультиплатформенність: один і той самий клас Serializable працює на Android, iOS (KMP) і серверному Kotlin.
Вибір між бібліотеками зводиться до компромісу швидкість-гнучкість. Gson хороший для прототипів і проєктів на Java — він не потребує анотацій і працює «з коробки». Moshi займає середню позицію: codegen через @JsonClass(generateAdapter = true) дає швидкість, близьку до kotlinx.serialization, а reflection-режим — гнучкість Gson. kotlinx.serialization — найшвидший варіант для чистих Kotlin-проєктів, але потребує Kotlin 1.4+ і плагіна Kotlin Serialization у Gradle.
| Бібліотека | Механізм | Швидкість | KMP |
|---|---|---|---|
| Gson | Reflection | Низька | Ні |
| Moshi | Reflection / Codegen | Середня / Висока | Ні |
| kotlinx.serialization | Compiler codegen | Висока | Так |
Type mismatch — ситуація, коли JSON містить значення одного типу, а модель очікує інший. Сервер надіслав рядок "42" замість числа або число 1 замість булевого true. На iOS JSONDecoder за замовчуванням викине DecodingError.typeMismatch, на Android Gson спробує перетворити, а Moshi та kotlinx.serialization потребують явних адаптерів. Рішення — використовувати lenient стратегії або кастомні десеріалізатори для конкретних полів.
Коли сервер не включає опціональне поле, код падає з помилкою. Optional поля на Swift і nullable типи в Kotlin вирішують проблему: якщо поле null або відсутнє в JSON, властивість отримує значення nil/null, а застосунок продовжує роботу. Для обов’язкових полів варто перевіряти їх наявність на рівні API-клієнта до десеріалізації. Moshi та kotlinx.serialization за замовчуванням потребують усі поля — nullable-маркування та default-значення знімають це обмеження.
Зміна структури JSON на сервері — часте джерело production-крашів. Стандартна практика — версіонування схеми через поле version у кореневому об’єкті та підтримка 2-3 попередніх версій на клієнті. kotlinx.serialization дозволяє оголосити кілька моделей під різні версії та вибирати потрібну за полем version після первинного парсингу в JsonElement. Додатковий захист — ignoreUnknownKeys для нових полів і default-значення для полів, які можуть бути видалені.
| Помилка | Симптом | Бібліотека із захистом |
|---|---|---|
| Type mismatch | DecodingError / виключення | kotlinx — coerceInputValues = true |
| Відсутність поля | Краш при зверненні | Moshi — @Transient + default |
| Невірний формат дати | Помилка декодингу | JSONDecoder — dateDecodingStrategy |
| Зайві поля | Ігноруються або краш | kotlinx — ignoreUnknownKeys = true |
| Null у non-null полі | Краш у runtime | Moshi — lenient із @Nullable |
Логування помилок десеріалізації — обов’язкова практика в production. Обгорніть decode в do/catch, логуйте raw JSON і тип очікуваної моделі в Crashlytics або Sentry. Це дозволить швидко визначити, яке поле якого API зламалося і на якій версії застосунку. Без логування помилка десеріалізації виглядає як загадковий краш без контексту.
Часті запитання
Парсинг — розбір структурованого тексту на складові елементи без обов’язкового створення типізованої моделі. Десеріалізація — окремий випадок парсингу, результатом якого є повноцінний об’єкт мови з відомими типами властивостей. Парсинг може бути потоковим, десеріалізація — завжди створює повний об’єкт.
Для проєкту на чистому Kotlin рекомендується kotlinx.serialization — вона інтегрована в компілятор, не використовує reflection і підтримує Kotlin Multiplatform. Для існуючого проєкту на Java — Moshi з code generation. Gson краще залишити для легасі-проєктів, де його заміна потребуватиме значних трудозатрат.
На iOS використовуйте keyDecodingStrategy = .convertFromSnakeCase у JSONDecoder. На Android у kotlinx.serialization використовуйте @SerialName для кожного поля. У Moshi застосовуйте @Json(name="field_name") або глобальний JsonAdapter.Factory. Єдиний стиль на рівні проєкту — best practice, узгоджений у контракті API.
Найчастіше причина — неочікуваний null від сервера на поле, оголошене як обов’язкове. У розробці сервер повертає повні дані, у production — скорочену відповідь. Рішення: маркувати всі потенційно відсутні поля як nullable (Kotlin) або optional (Swift), використовувати ignoreUnknownKeys та default-значення.
Code generation (Moshi codegen, kotlinx.serialization, Codable) працює в 2-4 рази швидше за reflection у бенчмарках Google. Крім швидкості, кодогенерація безпечніша за типами, не потребує metadata класів у runtime, і помилки типів відловлюються на етапі компіляції, а не в момент десеріалізації.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також