ГлавнаяБлогОшибка Gson в Kotlin: почему baseStat равен 0 и как исправить
Python

Ошибка Gson в Kotlin: почему baseStat равен 0 и как исправить

Разбираем баг Gson в Kotlin: поле baseStat всегда 0 из-за удаления @SerializedName. Узнайте, как Gson маппит JSON, почему молчит и как защититься от null. Читать 5 минут.

Al
Редакция Algolitalgolit.ru
5 мин чтения6 июля 2026 г.

Почему baseStat стал нулём после удаления @SerializedName?

Представьте: вы работаете над Pokedex-приложением на Kotlin. Всё работает, но коллега "наводит порядок" и удаляет строчку, которая кажется лишней. После этого каждый покемон получает baseStat = 0, хотя сервер присылает 45 или 49. Gson не выдаёт ошибок, не пишет в лог — просто молча подставляет ноль. Знакомо? Если вы сможете объяснить причину, вы понимаете Gson глубже, чем большинство джуниоров. А ещё это любимый вопрос на собеседованиях.

Как Gson на самом деле маппит JSON на Kotlin-классы

Когда приходит 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 перестал сопоставлять.

Как исправить: два пути

  1. Переименовать свойство в base_stat — работает, но ломает camelCase-конвенцию Kotlin. Не рекомендуется.
  2. Вернуть @SerializedName("base_stat") — сохраняет чистое имя и правильно маппит. Это правильное решение.

Почему Gson молчит, а не падает с ошибкой?

Многие ожидают, что отсутствие ключа вызовет исключение. Но Gson считает пропущенный ключ допустимым. Он собирает объект, заполняет найденные поля, а остальные оставляет со значениями по умолчанию. Представьте, что Gson заполняет бумажную анкету: оставить поле пустым — нормально, он не порвёт анкету из-за пустой клетки.

Вот значения по умолчанию для разных типов при отсутствии ключа:

  • Int, Long0
  • Double, Float0.0
  • Booleanfalse
  • String, любой объект, Listnull

Так что отсутствующий Int молча становится нулём, а отсутствующая Stringnull. Никаких сообщений.

Когда Gson всё-таки бросает исключение?

Gson строг только в двух случаях:

  • Некорректный JSON — сломан синтаксис (пропущена скобка, лишняя запятая). Gson не может распарсить → исключение.
  • Несоответствие типов — ключ есть, но значение не того типа, например, "base_stat": "hello" вместо числа. Gson пытается преобразовать, не может → исключение.

Запомните правило: Gson снисходителен к отсутствующим данным и строг к сломанным или неправильно типизированным. Пустая клетка — норма, рваная анкета или банан в поле для числа — нет.

Страшная часть: Gson против null-безопасности Kotlin

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 не сдержал. Вы обезвреживаете бомбу до того, как она взведена.

Ответ для собеседования: Gson и Kotlin в двух словах

  • Как Gson сопоставляет JSON и Kotlin? Точное совпадение имени свойства или @SerializedName для другого ключа. Нет совпадения — значение по умолчанию.
  • Поле пришло как 0/null, ошибки нет. Почему? Ключ JSON не совпал с именем свойства. Отсутствие ключа — не ошибка для Gson, поле получает значение по умолчанию.
  • Когда Gson бросает исключение? При некорректном JSON или несоответствии типов. Пропущенный ключ — нет.
  • В чём опасность non-null типов Kotlin? Gson через рефлексию может записать null в non-null поле, вызывая отложенный краш. Моделируйте JSON-поля как nullable, если API может их пропустить.

Всё это не про запоминание Gson. Это про понимание: Gson молчит именно там, где вы ждёте крика, и это молчание делает такие баги коварными.

Практический вывод: что сделать прямо сейчас

Откройте свой проект, найдите все data class’ы, которые используются с Gson (или Retrofit + Gson). Проверьте: все ли поля, которые могут отсутствовать в JSON, помечены как nullable (?). Убедитесь, что для всех несовпадающих имён стоит @SerializedName. И никогда не удаляйте эту аннотацию, даже если кажется, что она лишняя — она держит ваш код на плаву.

#Gson#Kotlin#JSON#баги#null-безопасность
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →