Moshi to nowoczesna biblioteka JSON od Square, stworzona specjalnie dla Kotlin i Android z uwzględnieniem ograniczeń Gson. Jest w pełni kompatybilna z null-bezpieczeństwem Kotlin, generuje kod na etapie kompilacji i nie używa refleksji, co zwiększa wydajność i niezawodność. Według danych Square Moshi, 2024, Moshi zapewnia przewidywalną serializację i obsługuje niestandardowe adaptery dla dowolnych typów danych.
Najważniejsze
Moshi to biblioteka JSON dla JVM, Android i Kotlin Multiplatform, stworzona przez Square (twórców OkHttp i Retrofit). W przeciwieństwie do Gson, Moshi nie polega na refleksji — adaptery są generowane na etapie kompilacji przez adnotację @JsonClass(generateAdapter = true). To sprawia, że Moshi jest szybszy, bezpieczniejszy i bardziej przewidywalny w pracy z konstrukcjami specyficznymi dla Kotlin.
Główna różnica Moshi od poprzedników to rezygnacja z refleksji. Refleksja pozwala Gson pracować z dowolną klasą bez przygotowania, ale kosztem wolnej inicjalizacji, braku możliwości optymalizacji przez kompilator i ryzyka błędów w czasie wykonania. Moshi wymaga jawnego wskazania klas do kodogeneracji, ale w zamian daje szybkość ręcznie pisanego kodu i pełne bezpieczeństwo typów na etapie kompilacji.
// Podłączanie Moshi w 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"
}
// Prosty model z kodogeneracją
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_id")
val id: Int,
val name: String,
val email: String,
val avatar: String? = null
)
// Użycie
val moshi = Moshi.Builder()
.build()
val jsonAdapter = moshi.adapter(User::class.java)
Aby rozpocząć pracę z Moshi, należy dodać zależności w build.gradle i adnotować modele. Moshi.Builder służy jako punkt wejścia: przez niego dodaje się wbudowane adaptery dla standardowych typów, niestandardowe adaptery i konfiguruje zachowanie biblioteki. Moshi obsługuje adaptery dla Date, Enum, Collection i Map od razu po wyjęciu z pudełka, ale dla klas Kotlin wymagany jest moduł moshi-kotlin. W przeciwieństwie do Gson, Moshi nie używa refleksji dla klas Kotlin domyślnie — w tym celu podłącza się KotlinJsonAdapterFactory, który służy jako rozwiązanie zapasowe, gdy kodogeneracja nie jest stosowana lub klasa nie jest adnotowana @JsonClass. Takie podejście gwarantuje, że programista jawnie wybiera między wydajnością kodogeneracji a elastycznością refleksji dla każdej konkretnej klasy.
Po zbudowaniu Moshi przez Builder programista otrzymuje instancję Moshi i żąda adaptera dla potrzebnej klasy. JsonAdapter to centralny obiekt, który wykonuje serializację przez toJson() i deserializację przez fromJson(). Moshi automatycznie używa wygenerowanego adaptera, jeśli klasa jest adnotowana @JsonClass(generateAdapter = true), w przeciwnym razie stosuje refleksyjny KotlinJsonAdapterFactory jako rozwiązanie zapasowe. Takie podejście łączy szybkość kodogeneracji z elastycznością mechanizmu refleksyjnego dla projektów dowolnej skali i poziomu złożoności. Moshi doskonale nadaje się zarówno do małych aplikacji, jak i dużych projektów korporacyjnych z setkami modeli danych.
// Konfiguracja Moshi z KotlinJsonAdapterFactory
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(LocalDateAdapter())
.build()
// Użycie adaptera
val adapter = moshi.adapter(User::class.java)
// Serializacja
val user = User(1, "Alice", "alice@test.com")
val json = adapter.toJson(user)
// Deserializacja
val jsonString = """{"user_id":2,"name":"Bob","email":"bob@test.com"}"""
val parsedUser = adapter.fromJson(jsonString)
// Praca z listą
val listAdapter = moshi.adapter(
Types.newParameterizedType(
List::class.java,
User::class.java
)
)
Moshi używa adnotacji do konfiguracji serializacji i obsługi niestandardowych typów. @Json(name = "...") ustawia klucz JSON dla pola. @Transient wyklucza pole z serializacji. @JsonClass(generateAdapter = true) włącza kodogenerację. Dla niestandardowej logiki Moshi udostępnia adnotacje @ToJson i @FromJson, które można umieścić w osobnej klasie adaptera.
Adnotacja @Json zastępuje Gson-owski @SerializedName i działa podobnie: pole kotlinName jest powiązane z kluczem JSON „kotlin_name". Dla typów, których Moshi nie potrafi serializować domyślnie (np. LocalDate), programista tworzy klasę z metodami @ToJson i @FromJson. Adaptery rejestruje się przez Moshi.Builder.add() i stosuje globalnie lub do konkretnego typu. Moshi obsługuje sealed class i polimorficzną serializację przez @JsonClass z jawnym wskazaniem dyskryminatora, co pozwala pracować z hierarchiami typów w JSON bez ręcznego sprawdzania pól. Podczas deserializacji Moshi domyślnie ignoruje nieznane klucze w JSON, co zapewnia wsteczną kompatybilność przy dodawaniu nowych pól po stronie serwera bez zmiany kodu klienta. Do debugowania można włączyć tryb ścisły przez failOnUnknown, który zgłasza wyjątek przy wykryciu nieznanych kluczy.
// Niestandardowy adapter dla 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)
}
}
// Model z adnotacjami 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
)
// Rejestracja adaptera
val moshi = Moshi.Builder()
.add(LocalDateAdapter())
.add(KotlinJsonAdapterFactory())
.build()
Porównanie Moshi i Gson to częste pytanie przy wyborze biblioteki JSON dla projektu Android. Moshi wygrywa w nowoczesnym rozwoju Kotlin dzięki kodogeneracji, null-bezpieczeństwu i szybkości. Gson pozostaje aktualny dla projektów Java, legacy-kodu i scenariuszy, gdzie ważna jest minimalna konfiguracja. Różnica staje się zauważalna przy dużych ilościach danych i złożonych modelach.
Testy wydajności pokazują, że Moshi z kodogeneracją działa 2-5 razy szybciej niż Gson na operacjach serializacji i deserializacji. Kluczową zaletą Moshi jest poprawne obsługiwanie null-bezpieczeństwa Kotlin: jeśli w JSON brakuje pola, a w modelu jest ono zadeklarowane jako non-null bez wartości domyślnej, Moshi zgłasza wyjątek na etapie deserializacji, zapobiegając ukrytym błędom.
| Cecha | Gson | Moshi |
|---|---|---|
| Mechanizm | refleksja | kodogeneracja / refleksja |
| Null safety | nie uwzględnia | pełne wsparcie Kotlin |
| Szybkość | średnia | wysoka |
| Wartości domyślne | nie obsługuje | obsługuje |
| Kotlin Multiplatform | nie | tak |
| Rozmiar biblioteki | ~240 Kb | ~150 Kb |
Wybór między Moshi a Gson zależy od kontekstu projektu. Nowe projekty na Kotlin zyskują na Moshi dzięki bezpieczeństwu typów i wydajności. Gson pozostaje rozsądnym wyborem do obsługi kodu Java, dynamicznych struktur JSON lub gdy prostota podłączenia jest ważniejsza niż szybkość. Dla Kotlin Multiplatform Moshi jest jedynym z dwóch wariantów obsługującym tę platformę.
Przy migracji z Gson na Moshi główne zmiany dotyczą adnotacji i adapterów. Gson-owskie @SerializedName zastępuje się @Json(name = "..."), a niestandardowe JsonSerializer/JsonDeserializer — parą @ToJson/@FromJson. Dla modeli z wartościami domyślnymi i polami nullable Moshi zachowuje się bardziej przewidywalnie: jeśli w JSON brakuje non-null pola bez wartości domyślnej, Moshi zgłasza JsonDataException, zapobiegając ukrytym NPE. Integracja z Retrofit przez MoshiConverterFactory jest dodawana jedną zależnością i nie wymaga zmiany architektury warstwy sieciowej. Do obfuskacji przez ProGuard lub R8 należy dodać reguły zachowania klas adnotowanych @JsonClass i wygenerowanych adapterów, w przeciwnym razie serializacja zepsuje się w wersji release. Ogólnie migracja z Gson na Moshi jest uzasadniona w nowych projektach Kotlin, gdzie ważne są wydajność i bezpieczeństwo typów.
// Porównanie serializacji: Gson vs Moshi
data class Sample(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
// Gson: działa przez refleksję
val gson = Gson()
val fromGson = gson.fromJson("""{"name":"test"}""",
Sample::class.java)
// count = 0 (default), ale null-bezpieczeństwo nie jest sprawdzane
// Moshi: wymaga adaptera, null-bezpieczeństwo jawne
@JsonClass(generateAdapter = true)
data class SampleMoshi(
val name: String,
val count: Int,
val tags: List<String> = listOf()
)
Często zadawane pytania
Moshi to biblioteka JSON od Square dla Kotlin i Android, używająca kodogeneracji zamiast refleksji. Zapewnia wysoką wydajność, poprawne obsługiwanie null-bezpieczeństwa Kotlin i kompatybilność z Kotlin Multiplatform.
Moshi przewyższa Gson szybkością (2-5 razy szybszy dzięki kodogeneracji), bezpieczeństwem (uwzględnia null-adnotacje Kotlin) i rozmiarem (mniejszy o ~90 Kb). Moshi obsługuje również Kotlin Multiplatform i wartości domyślne w data class.
@JsonClass(generateAdapter = true) nakazuje Moshi wygenerować adapter dla danej klasy na etapie kompilacji. Wygenerowany adapter wykonuje serializację bezpośrednio, bez refleksji, co daje maksymalną wydajność.
Utwórz klasę z metodami adnotowanymi @ToJson (serializacja) i @FromJson (deserializacja). Zarejestruj instancję przez Moshi.Builder.add(). Moshi automatycznie znajdzie i zastosuje adapter podczas pracy z odpowiednim typem.
Tak, Moshi obsługuje Kotlin Multiplatform od wersji 1.13.0. To czyni go jedynym popularnym rozwiązaniem JSON dla projektów KMP, pozwalając na używanie wspólnego kodu serializacji na wszystkich docelowych platformach.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również