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 проекти, наследен код и сценарии, където минималната конфигурация е важна. Разликата става забележима при големи обеми данни и сложни модели.
Тестовете за производителност показват, че Moshi с генериране на код работи 2-5 пъти по-бързо от Gson при операции по сериализация и десериализация. Ключовото предимство на Moshi е правилното обработване на null-безопасността на Kotlin: ако поле липсва в JSON и в модела е декларирано като non-null без стойност по подразбиране, Moshi хвърля изключение на етапа на десериализация, предотвратявайки скрити грешки.
| Характеристика | Gson | Moshi |
|---|---|---|
| Механизъм | рефлексия | генериране на код / рефлексия |
| Null safety | не взема предвид | пълна поддръжка на Kotlin |
| Скорост | средна | висока |
| Стойности по подразбиране | не поддържа | поддържа |
| 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. За модели със стойности по подразбиране и nullable полета Moshi се държи по-предвидимо: ако non-null поле без стойност по подразбиране липсва в JSON, Moshi хвърля JsonDataException, предотвратявайки скрити NPE. Интеграцията с Retrofit чрез MoshiConverterFactory се добавя с една зависимост и не изисква промяна на архитектурата на мрежовия слой. За объркване чрез ProGuard или R8 трябва да се добавят правила за запазване на класове, анотирани с @JsonClass, и генерирани адаптери, в противен случай сериализацията ще се счупи в release версията. Като цяло, миграцията от Gson към Moshi е оправдана в нови Kotlin проекти, където производителността и типовата безопасност са важни.
// Сравнение на сериализация: Gson срещу 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 Kb). Moshi също поддържа Kotlin Multiplatform и стойности по подразбиране в 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също