Gson — библиотека на Google за сериализация на Java обекти в JSON и обратно, широко използвана в Android разработката. Тя позволява преобразуване на сложни графи от обекти в компактни JSON низове без ръчно писане на парсери. По данни на Google Gson, 2024, библиотеката има над 23 хиляди звезди в GitHub и остава едно от най-популярните решения за работа с JSON в екосистемата на Java и Kotlin.
Основни точки
Gson — е Java библиотека, разработена от Google за преобразуване на обекти в JSON представяне и обратно. Тя използва рефлексия за анализ на структурата на класовете, което позволява работа без предварителна конфигурация. Gson поддържа произволни Java обекти, колекции, масиви, генерици и вложени класове. Библиотеката не изисква анотации за основна употреба, но ги предоставя за фина настройка. Основният недостатък на рефлексията е намалената производителност при инициализация и невъзможността за оптимизация на етапа на компилиране, което е особено забележимо при студен старт на Android приложение при десериализация на стотици модели. Въпреки това, Gson остава надежден избор за повечето проекти благодарение на стабилността и обширната документация.
Gson беше пуснат от Google през 2008 г. и бързо се превърна в де факто стандарт за JSON в Android приложенията. Преди появата на Moshi и kotlinx.serialization, Gson оставаше единственият популярен избор за Kotlin проекти. Простота на свързване — добавяне на една зависимост в build.gradle — и липсата на задължителни анотации направиха Gson популярен сред разработчици от всички нива.
// Добавяне на Gson в build.gradle
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
}
// Основна употреба
data class User(
val id: Int,
val name: String,
val email: String
)
val gson = Gson()
val user = User(1, "John", "john@test.com")
val json = gson.toJson(user)
println(json) // {"id":1,"name":"John","email":"john@test.com"}
Освен основна сериализация, Gson предоставя GsonBuilder за конфигуриране на поведението: форматиране на дати, изключване на HTML escaping, регистър на ключове и персонализирани инстанции. GsonBuilder също така позволява регистриране на персонализирани JsonSerializer и JsonDeserializer за типове, които библиотеката не може да обработи автоматично. Гъвкавостта на конфигурацията прави GsonBuilder незаменим и полезен инструмент при адаптиране на библиотеката към специфичните изисквания на проекта в съвременната Android разработка.
toJson преобразува Java обект в JSON низ, анализирайки полетата му чрез рефлексия. По подразбиране Gson включва всички полета, освен transient и static. Методът поддържа всички типове: примитиви, обекти, колекции и масиви. fromJson извършва обратното преобразуване, приема JSON низ и клас на целевия обект, и връща инстанция с попълнени полета.
При сериализация Gson рекурсивно обхожда всички полета на обекта, включително вложените. Цикличните референции водят до StackOverflowError, затова те трябва да бъдат изключени чрез анотация @Expose или персонализиран адаптер. За колекции Gson запазва типа на елементите, но при десериализация на списък с генерици е необходим TypeToken за запазване на информацията за типа.
// data class с вложен обект
data class Address(
val city: String,
val street: String
)
data class Employee(
val id: Int,
val name: String,
val address: Address
)
val gson = Gson()
val employee = Employee(1, "Alice",
Address("New York", "5th Ave"))
// Сериализация в JSON
val json = gson.toJson(employee)
// Десериализация от JSON
val jsonString = """
{"id":2,"name":"Bob","address":{"city":"London","street":"Baker St"}}
"""
val parsed = gson.fromJson(jsonString, Employee::class.java)
Gson предоставя набор от анотации за управление на процеса на сериализация. @SerializedName задава име на JSON ключ, различно от името на полето. @Expose управлява включването на полето в сериализация: Gson, създаден чрез GsonBuilder.excludeFieldsWithoutExposeAnnotation(), ще обработва само полета с @Expose. @Since и @Until контролират версионирането на полета.
Анотацията @SerializedName решава проблема с несъответствието на имена: сървърът може да използва snake_case, докато в кода е приет camelCase. Анотацията приема стойност и опционални алтернативи за обратна съвместимост. @Expose позволява скриване на чувствителни полета (пароли, токени) от сериализация, като ги маркира като @Expose(serialize = false). Освен включване и изключване, @Expose може да се комбинира с GsonBuilder.excludeFieldsWithoutExposeAnnotation за създаване на бял списък от полета, което помага за контролиране на повърхността за атака при сериализация на обекти с голям брой полета.
// Модел с Gson анотации
data class UserResponse(
@SerializedName("user_id")
val userId: Int,
@SerializedName("full_name",
alternate = [Alternative("name")])
val fullName: String,
@Expose(serialize = false)
val password: String
)
// Gson с @Expose филтър
val gson = GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setPrettyPrinting()
.create()
val user = UserResponse(1, "John", "secret123")
println(gson.toJson(user))
// {"user_id":1,"full_name":"John"} — password excluded
Проблемът с генериците в Java и Kotlin се състои в изтриването на типове по време на компилиране. Когато Gson десериализира List<User>, той не знае типа на елемента и връща List<Map<String, Any>>. За да се запази информацията за типа, Gson предоставя TypeToken — абстрактен клас, който улавя параметъра на типа чрез анонимен клас. Без TypeToken, разработчикът трябва ръчно да преобразува всеки елемент от Map в целевия тип, което води до обемист код и загуба на производителност.
TypeToken решава проблема с изтриването на типове. Разработчикът създава анонимен наследник на TypeToken с необходимия параметър на типа и Gson използва информацията от сигнатурата на класа за правилна десериализация. TypeToken работи също с Map, Set и всякакви други параметризирани типове, включително вложени генерици. По-специално, за Map<String, List<User>> е необходим TypeToken с пълна сигнатура на вложения тип, иначе Gson десериализира стойностите като List<Map<String, Any>> вместо List<User>.
// TypeToken за десериализация на списък
data class Product(
val id: Int,
val title: String,
val price: Double
)
val jsonArray = """
[
{"id":1,"title":"Phone","price":599.0},
{"id":2,"title":"Laptop","price":1299.0}
]
"""
val gson = Gson()
val listType = object : TypeToken<List<Product>>() {}
val products: List<Product> =
gson.fromJson(jsonArray, listType.type)
// Персонализиран десериализатор
class LocalDateAdapter :
JsonDeserializer<LocalDate> {
override fun deserialize(
json: JsonElement,
typeOfT: java.lang.reflect.Type,
context: JsonDeserializationContext
): LocalDate {
return LocalDate.parse(json.asString)
}
}
За персонализирана логика на сериализация Gson поддържа интерфейсите JsonSerializer и JsonDeserializer. Те се регистрират чрез GsonBuilder.registerTypeAdapter() и позволяват обработка на типове, които библиотеката не може да сериализира автоматично: дати Java 8, Enum с нестандартни стойности или класове на трети страни без достъп до изходния код. При имплементиране на адаптер е важно да се следи производителността: извикване на рефлексия вътре в персонализиран адаптер унищожава предимствата на ръчното управление, затова се предпочитат директни извиквания на методи и полета. В екосистемата на Gson съществува и модул gson-extras, който предоставя адаптери за често срещани типове като UUID, Optional и типовете дати Joda-Time.
GsonBuilder предоставя десетки методи за фина настройка на сериализацията. setPrettyPrinting добавя отстъпи и нови редове в изходния JSON за четимост. disableHtmlEscaping изключва escaping на HTML символи в низовете. setDateFormat задава формат на датата, което е критично при работа със сървъри, използващи нестандартно представяне на времето. setLenient включва свободен режим на парсиране, който игнорира някои грешки при форматиране на JSON. addDeserializationExclusionStrategy позволява програмно изключване на полета от десериализация въз основа на персонализирани стратегии. За отстраняване на грешки, методът setPrettyPrinting е полезен в комбинация с логване — прави JSON отговорите четими в логовете и улеснява намирането на несъответствия.
Важна възможност на GsonBuilder е управлението на версионирането на полета чрез анотациите @Since и @Until. Разработчикът задава версията на обекта чрез setVersion, а Gson автоматично включва или изключва полета в зависимост от тяхната версионна анотация. Това е полезно при еволюция на API, когато един и същ модел се използва за различни версии на сървърния протокол. GsonBuilder също поддържа регистриране на TypeAdapterFactory за глобална обработка на семейства типове и complexMapKeySerialization за правилна работа със сложни Map ключове.
Често задавани въпроси
Gson — е библиотека на Google за преобразуване на Java обекти в JSON и обратно. Тя се използва широко в Android приложения за парсване на отговори от сървър, сериализация на заявки и запазване на данни в локално хранилище.
По подразбиране Gson пропуска полета с null при сериализация. За включване на null стойности използвайте GsonBuilder.serializeNulls(). При десериализация липсващите в JSON полета остават null или приемат стойност по подразбиране за типа.
Moshi не използва рефлексия за Kotlin класове, което осигурява по-висока производителност и предвидимо поведение. Moshi също така правилно обработва null-безопасността на Kotlin, докато Gson може да десериализира null в non-null поле, причинявайки изключение.
@SerializedName свързва JSON ключ с поле на клас, когато имената им не съвпадат. Например, за поле kotlinName и JSON ключ „kotlin_name”, анотацията @SerializedName(„kotlin_name”) осигурява правилно преобразуване.
TypeToken — е абстрактен клас, който улавя параметъра на типа чрез анонимен клас. Той е необходим за десериализация на колекции и други параметризирани типове, тъй като поради изтриване на типовете, Gson не може да възстанови типа на елемента по време на изпълнение.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също