Разбираем баг Gson в Kotlin: поле baseStat всегда 0 из-за удаления @SerializedName. Узнайте, как Gson маппит JSON, почему молчит и как защититься от null. Читать 5 минут.
Представьте: вы работаете над Pokedex-приложением на Kotlin. Всё работает, но коллега "наводит порядок" и удаляет строчку, которая кажется лишней. После этого каждый покемон получает baseStat = 0, хотя сервер присылает 45 или 49. Gson не выдаёт ошибок, не пишет в лог — просто молча подставляет ноль. Знакомо? Если вы сможете объяснить причину, вы понимаете Gson глубже, чем большинство джуниоров. А ещё это любимый вопрос на собеседованиях.
Когда приходит JSON, Gson действует просто: читает ключ из JSON, ищет в Kotlin-классе свойство с точно таким же именем. Нашёл — записывает значение. Не нашёл — оставляет свойству значение по умолчанию и идёт дальше. Никакой магии, только точное совпадение имён.
Взгляните на "улучшенный" класс:
data class PokemonStat(
val baseStat: Int, // @SerializedName удалён
val stat: StatInfo
)Ключ в JSON — base_stat, а Kotlin-свойство — baseStat. Это разные строки. Gson ищет свойство с именем base_stat, не находит и оставляет baseStat со значением по умолчанию. Для Int это 0. Вот и баг.
Аннотация @SerializedName("base_stat") была не лишней — это инструкция для Gson: "в Kotlin свойство называется baseStat, но в JSON ищи base_stat". Удалили инструкцию — Gson перестал сопоставлять.
base_stat — работает, но ломает camelCase-конвенцию Kotlin. Не рекомендуется.@SerializedName("base_stat") — сохраняет чистое имя и правильно маппит. Это правильное решение.Многие ожидают, что отсутствие ключа вызовет исключение. Но Gson считает пропущенный ключ допустимым. Он собирает объект, заполняет найденные поля, а остальные оставляет со значениями по умолчанию. Представьте, что Gson заполняет бумажную анкету: оставить поле пустым — нормально, он не порвёт анкету из-за пустой клетки.
Вот значения по умолчанию для разных типов при отсутствии ключа:
Int, Long → 0Double, Float → 0.0Boolean → falseString, любой объект, List → nullТак что отсутствующий Int молча становится нулём, а отсутствующая String — null. Никаких сообщений.
Gson строг только в двух случаях:
"base_stat": "hello" вместо числа. Gson пытается преобразовать, не может → исключение.Запомните правило: Gson снисходителен к отсутствующим данным и строг к сломанным или неправильно типизированным. Пустая клетка — норма, рваная анкета или банан в поле для числа — нет.
Kotlin обещает, что поле без вопросительного знака никогда не будет null. Компилятор это гарантирует. Но Gson нарушает это обещание: он проникает через рефлексию и напрямую записывает значения в поля, минуя все проверки Kotlin. Если ключ в JSON отсутствует, Gson тихо кладёт null в non-null поле String — никакой ошибки, а гарантия "никогда не null" остаётся формальной.
Позже, в другом месте, ваш код делает очевидное:
val length = pokemon.name.length // name был тайно null
// 💥 NullPointerExceptionИ краш происходит далеко от парсинга JSON. Попробуй отлови такую ошибку в час ночи.
Именно поэтому поля, приходящие из JSON, безопаснее делать nullable:
data class Sprites(
@SerializedName("front_default")
val frontDefault: String? // честно признаём, что может быть null
)Знак ? говорит Kotlin правду — это поле действительно может быть null — и компилятор заставит вас обработать пустой случай, вместо того чтобы верить обещанию, которое Gson не сдержал. Вы обезвреживаете бомбу до того, как она взведена.
@SerializedName для другого ключа. Нет совпадения — значение по умолчанию.null в non-null поле, вызывая отложенный краш. Моделируйте JSON-поля как nullable, если API может их пропустить.Всё это не про запоминание Gson. Это про понимание: Gson молчит именно там, где вы ждёте крика, и это молчание делает такие баги коварными.
Откройте свой проект, найдите все data class’ы, которые используются с Gson (или Retrofit + Gson). Проверьте: все ли поля, которые могут отсутствовать в JSON, помечены как nullable (?). Убедитесь, что для всех несовпадающих имён стоит @SerializedName. И никогда не удаляйте эту аннотацию, даже если кажется, что она лишняя — она держит ваш код на плаву.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →