Moshi — сучасна JSON-бібліотека від Square, створена спеціально для Kotlin і Android з урахуванням обмежень Gson. Вона повністю сумісна з null-безпекою Kotlin, генерує код на етапі компіляції і не використовує рефлексію, що підвищує продуктивність і надійність. За даними Square Moshi, 2024, Moshi забезпечує передбачувану серіалізацію і підтримує кастомні адаптери для будь-яких типів даних.
Головне
Moshi — це JSON-бібліотека для JVM, Android і Kotlin Multiplatform, створена Square (авторами OkHttp і Retrofit). На відміну від Gson, Moshi не покладається на рефлексію — адаптери генеруються на етапі компіляції через анотацію @JsonClass(generateAdapter = true). Це робить Moshi швидшою, безпечнішою та передбачуванішою в роботі з Kotlin-специфічними конструкціями.
Основна відмінність Moshi від попередників — відмова від рефлексії. Рефлексія дозволяє Gson працювати з будь-яким класом без підготовки, але платою є повільна ініціалізація, неможливість оптимізації компілятором і ризик помилок під час виконання. Moshi вимагає явного зазначення класів для кодогенерації, але натомість дає швидкість рукописного коду та повну типобезпеку на етапі компіляції.
// Підключення Moshi в build.gradle
dependencies {
implementation "com.squareup.moshi:moshi:1.15.0"
implementation "com.squareup.moshi:moshi-kotlin:1.15.0"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.15.0"
}
// Проста модель з кодогенерацією
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_id")
val id: Int,
val name: String,
val email: String,
val avatar: String? = null
)
// Використання
val moshi = Moshi.Builder()
.build()
val jsonAdapter = moshi.adapter(User::class.java)
Для початку роботи з Moshi потрібно додати залежності в build.gradle та анотувати моделі. Moshi.Builder слугує точкою входу: через нього додаються вбудовані адаптери для стандартних типів, кастомні адаптери та налаштовується поведінка бібліотеки. Moshi підтримує адаптери для Date, Enum, Collection і Map з коробки, але для Kotlin-класів потрібен модуль moshi-kotlin. На відміну від Gson, Moshi не використовує рефлексію для Kotlin-класів за замовчуванням — для цього підключається KotlinJsonAdapterFactory, який слугує запасним варіантом, коли кодогенерація не застосовується або клас не анотований @JsonClass. Такий підхід гарантує, що розробник явно обирає між продуктивністю кодогенерації та гнучкістю рефлексії для кожного конкретного класу.
Після збірки Moshi через Builder розробник отримує екземпляр Moshi та запитує адаптер для потрібного класу. JsonAdapter — центральний об'єкт, який виконує серіалізацію через toJson() та десеріалізацію через fromJson(). Moshi автоматично використовує згенерований адаптер, якщо клас анотований @JsonClass(generateAdapter = true), інакше застосовує рефлексивний KotlinJsonAdapterFactory як запасний варіант. Такий підхід поєднує швидкість кодогенерації з гнучкістю рефлексивного механізму для проектів будь-якого масштабу та рівня складності. Moshi чудово підходить як для невеликих додатків, так і для великих корпоративних проектів із сотнями моделей даних.
// Налаштування Moshi з KotlinJsonAdapterFactory
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(LocalDateAdapter())
.build()
// Використання адаптера
val adapter = moshi.adapter(User::class.java)
// Серіалізація
val user = User(1, "Alice", "alice@test.com")
val json = adapter.toJson(user)
// Десеріалізація
val jsonString = """{"user_id":2,"name":"Bob","email":"bob@test.com"}"""
val parsedUser = adapter.fromJson(jsonString)
// Робота зі списком
val listAdapter = moshi.adapter(
Types.newParameterizedType(
List::class.java,
User::class.java
)
)
Moshi використовує анотації для налаштування серіалізації та підтримки кастомних типів. @Json(name = "...") задає JSON-ключ для поля. @Transient виключає поле з серіалізації. @JsonClass(generateAdapter = true) вмикає кодогенерацію. Для кастомної логіки Moshi надає анотації @ToJson і @FromJson, які можна розмістити в окремому класі-адаптері.
Анотація @Json замінює Gson-івський @SerializedName і працює аналогічно: поле kotlinName пов'язується з JSON-ключем "kotlin_name". Для типів, які Moshi не вміє серіалізувати за замовчуванням (наприклад, LocalDate), розробник створює клас з методами @ToJson і @FromJson. Адаптери реєструються через Moshi.Builder.add() і застосовуються глобально або до конкретного типу. Moshi підтримує sealed class і поліморфну серіалізацію через @JsonClass з явним зазначенням дискримінатора, що дозволяє працювати з ієрархіями типів у JSON, не вдаючись до ручної перевірки полів. При десеріалізації Moshi за замовчуванням ігнорує невідомі ключі в JSON, що забезпечує зворотну сумісність при додаванні нових полів на стороні сервера без зміни клієнтського коду. Для налагодження можна увімкнути строгий режим через failOnUnknown, який викидає виняток при виявленні невідомих ключів.
// Кастомний адаптер для LocalDate
class LocalDateAdapter {
@ToJson
fun toJson(date: LocalDate): String {
return date.format(DateTimeFormatter.ISO_LOCAL_DATE)
}
@FromJson
fun fromJson(dateString: String): LocalDate {
return LocalDate.parse(dateString)
}
}
// Модель з анотаціями Moshi
@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "event_id")
val id: Int,
@Json(name = "event_date")
val date: LocalDate,
@Transient
val localCache: String? = null
)
// Реєстрація адаптера
val moshi = Moshi.Builder()
.add(LocalDateAdapter())
.add(KotlinJsonAdapterFactory())
.build()
Порівняння Moshi і Gson — часте питання при виборі JSON-бібліотеки для Android-проекту. Moshi виграє в сучасній Kotlin-розробці завдяки кодогенерації, null-безпеці та швидкості. Gson залишається актуальним для Java-проектів, legacy-коду та сценаріїв, де важлива мінімальна конфігурація. Різниця стає помітною на великих обсягах даних і складних моделях.
Тести продуктивності показують, що Moshi з кодогенерацією працює в 2–5 разів швидше Gson на операціях серіалізації та десеріалізації. Ключова перевага Moshi — коректна обробка null-безпеки Kotlin: якщо в JSON поле відсутнє, а в моделі воно оголошено як non-null без значення за замовчуванням, Moshi викидає виняток на етапі десеріалізації, запобігаючи прихованим помилкам.
| Характеристика | Gson | Moshi |
|---|---|---|
| Механізм | рефлексія | кодогенерація / рефлексія |
| Null safety | не враховує | повна підтримка Kotlin |
| Швидкість | середня | висока |
| Default values | не підтримує | підтримує |
| Kotlin Multiplatform | ні | так |
| Розмір бібліотеки | ~240 Kb | ~150 Kb |
Вибір між Moshi і Gson залежить від контексту проекту. Нові проекти на Kotlin виграють від Moshi завдяки типобезпеці та продуктивності. Gson залишається розумним вибором для підтримки Java-коду, динамічних структур JSON або коли простота підключення важливіша за швидкість. Для Kotlin Multiplatform Moshi є єдиним із двох варіантів, що підтримує цю платформу.
При міграції з Gson на Moshi основні зміни стосуються анотацій та адаптерів. Gson-івський @SerializedName замінюється на @Json(name = "..."), а кастомні JsonSerializer/JsonDeserializer — на пару @ToJson/@FromJson. Для моделей з default values і nullable-полями Moshi веде себе більш передбачувано: якщо в JSON відсутнє non-null поле без значення за замовчуванням, Moshi викидає JsonDataException, запобігаючи прихованим NPE. Інтеграція з Retrofit через MoshiConverterFactory додається однією залежністю і не вимагає зміни архітектури мережевого шару. Для обфускації через ProGuard або R8 необхідно додати правила збереження @JsonClass-анотованих класів і згенерованих адаптерів, інакше серіалізація зламається в релізній збірці. Загалом міграція з Gson на Moshi виправдана в нових Kotlin-проектах, де важливі продуктивність і типобезпека.
// Порівняння серіалізації: Gson vs Moshi
data class Sample(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
// Gson: працює через рефлексію
val gson = Gson()
val fromGson = gson.fromJson("""{"name":"test"}""",
Sample::class.java)
// count = 0 (default), але null-небезпека не перевіряється
// Moshi: вимагає адаптера, null-безпека явна
@JsonClass(generateAdapter = true)
data class SampleMoshi(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
Часто задавані питання
Moshi — це JSON-бібліотека від Square для Kotlin і Android, яка використовує кодогенерацію замість рефлексії. Вона забезпечує високу продуктивність, коректну обробку null-безпеки Kotlin і сумісність з Kotlin Multiplatform.
Moshi перевершує Gson за швидкістю (в 2–5 разів швидше завдяки кодогенерації), безпеці (враховує null-анотації Kotlin) та розміром (менше на ~90 Кб). Moshi також підтримує Kotlin Multiplatform і default values в data class.
@JsonClass(generateAdapter = true) вказує Moshi згенерувати адаптер для даного класу на етапі компіляції. Згенерований адаптер виконує серіалізацію безпосередньо, без рефлексії, що дає максимальну продуктивність.
Створіть клас з методами, анотованими @ToJson (серіалізація) і @FromJson (десеріалізація). Зареєструйте екземпляр через Moshi.Builder.add(). Moshi автоматично знайде і застосує адаптер при роботі з відповідним типом.
Так, Moshi підтримує Kotlin Multiplatform починаючи з версії 1.13.0. Це робить його єдиним популярним JSON-рішенням для KMP-проектів, дозволяючи використовувати спільний код серіалізації на всіх цільових платформах.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також