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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также