JavaRush /Курсы /Java Server /Простые JSON-значения

Простые JSON-значения

Java Server
10 уровень, 1 лекция
Открыта

1. JSON-документ = JSON-значение

Когда человек впервые видит JSON, он часто запоминает его как «что-то в фигурных скобках». Это нормальная стартовая иллюзия, примерно как думать, что интернет — это «страницы в браузере». На самом деле JSON намного проще и одновременно строже: JSON-документ — это одно JSON-значение. Да, иногда это значение выглядит как объект {...} или массив [...], но «атом» JSON — именно значение, и у значения есть тип.

В JSON существует всего шесть видов значений: строка, число, булево, null, объект и массив. Сегодня мы сознательно берём только «простые» (скалярные) типы: строка, число, boolean и null. Они встречаются буквально в каждом API: title и author — строки, id и count — числа, какие-нибудь isAvailableboolean, а comment может быть null. И вот тут начинается самое интересное: тип значения определяется не тем, «как это читается по-русски», а тем, как оно записано.

Чтобы почувствовать идею, полезно увидеть, что даже такой «маленький» JSON тоже валиден и является полноценным JSON-документом:

// Каждый из этих литералов — отдельный JSON-документ (ровно одно JSON-значение)
String jsonDoc1 = "true";            // boolean
String jsonDoc2 = "2008";            // number
String jsonDoc3 = "\"Clean Code\"";  // string (кавычки — часть JSON, поэтому они экранированы в Java-строке)
String jsonDoc4 = "null";            // null

Все четыре строки выше — валидные JSON-документы (каждый сам по себе). В реальном API так почти не делают на верхнем уровне (там обычно объект или массив), но внутри объекта такие значения встречаются постоянно. И если вы научитесь быстро видеть тип значения, у вас резко снизится уровень тревоги при чтении реальных ответов: вы будете понимать, где текст, где число, где флаг, а где «значения нет».

Для наглядности — маленькая «карта местности» того, куда сегодня вписываются наши типы:

flowchart TD
  V["JSON value"] --> S["String"]
  V --> N["Number"]
  V --> B["Boolean"]
  V --> Z["Null"]
  V --> O["Object"]
  V --> A["Array"]

2. Строки в JSON: кавычки и экранирование

Строки — самый «человеческий» тип в JSON. Названия книг, имена авторов, статусы вроде "PLANNED" — всё это строки. И как раз из-за привычности строк многие новички допускают самые обидные ошибки: используют одинарные кавычки, забывают про экранирование или случайно превращают число в строку (и потом удивляются, почему "10" ведёт себя не как 10). Поэтому сейчас мы договоримся о паре железных правил, и жить станет спокойнее.

Первое правило: строковый тип в JSON узнаётся по двойным кавычкам. Если вы видите "Clean Code" — это строка. Без кавычек это уже не JSON-строка, а одинарные кавычки сюда тоже не подходят.

Вот минимальные примеры JSON-строк (как отдельных JSON-документов):

// Внутри Java-строки мы храним JSON-строку, поэтому кавычки JSON нужно экранировать
String jsonTitle = "\"Clean Code\"";
String jsonAuthor = "\"Robert C. Martin\"";

// Печатаем как есть: увидите именно JSON-представление со скобками-кавычками
System.out.println(jsonTitle);  // "Clean Code"
System.out.println(jsonAuthor); // "Robert C. Martin"

Обратите внимание на «двойную реальность»: мы пишем Java-строку, внутри которой лежит JSON-строка. Поэтому кавычки нужно экранировать. Да, это выглядит как «кавычки в кавычках», и это нормально. Привыкайте: в backend-разработке вы постоянно описываете одни данные внутри других (HTTP внутри кода, JSON внутри HTTP, потом DTO внутри JSON… и так далее по спирали взросления).

Второе правило: внутри строки иногда нужно экранировать символы. Например, если вы хотите передать кавычку как часть текста, в JSON она должна быть записана как \". Аналогично, обратный слэш — это \\. Пример:

// Здесь внутри JSON-строки есть кавычки, поэтому они превращаются в \" (а в Java-строке — в \\")
String jsonWithQuotes = "\"He said: \\\"hi\\\"\"";
System.out.println(jsonWithQuotes); // "He said: \"hi\""

Чтобы не превращать лекцию в курс «Победи слэши», достаточно помнить популярные escape-последовательности. Вот небольшая табличка, которая обычно закрывает 90% реальности:

Что хотим внутри строки Как записать в JSON-строке
" \"
\ \\
перенос строки \n
табуляция \t

И ещё один практический нюанс: в JSON нет отдельного типа «символ» (как char в Java). Есть только строка. Поэтому "A" — строка длины 1, и это нормально.

Если связать это с нашим проектом ReadLater Starter, то почти все «человеческие» поля будут строками: title, author, status, externalId, comment. Даже если status выглядит как «перечисление», на уровне JSON это всё равно строка (про дисциплину DTO мы поговорим в отдельный уровень, но для чтения контракта сейчас важно именно это).

3. Числа в JSON: формат и кавычки

С числами в JSON всё кажется простым ровно до первого реального API. Потом внезапно выясняется, что 2008 и "2008" — это разные вещи, что 3,14 — не число (потому что запятая), что 01 — подозрительная запись, и что «а почему мой id вдруг стал строкой» — это не философский вопрос, а конкретная боль интеграции. Сейчас разберём базу, без математической академичности, но честно.

Главное правило: число в JSON пишется без кавычек. Если вы видите 2008 — это число. Если вы видите "2008" — это строка, хотя глазами хочется закричать «да это же год!». JSON не смотрит глазами, он смотрит по синтаксису.

Мини-набор примеров чисел как JSON-документов:

// Эти значения выглядят "как числа" и записаны без кавычек — значит, это JSON-number
String jsonYear = "2008";
String jsonCount = "0";
String jsonRating = "4.5";
String jsonNegative = "-1";

Да, дробная часть записывается через точку, а не через запятую. JSON в этом смысле «интернационален» и придерживается машинного формата, а не привычек конкретного человека и его клавиатуры.

Иногда вы встретите и научную нотацию (особенно если JSON приходит из каких-нибудь научных или финансовых источников). На уровне чтения контракта полезно хотя бы узнать эту форму:

String jsonScientific = "1e3"; // научная нотация: это 1000

Теперь про дисциплину. В спецификации JSON числа описаны довольно строго (например, ведущие нули в стиле 01 — недопустимы). Но на практике вы как разработчик делаете очень простую вещь: вы договариваетесь, какие поля в контракте — числа, и следите, чтобы они не «уплывали» в строки.

В ReadLater почти гарантированно будут числа для идентификаторов и счётчиков: id, count. Позже, когда мы будем отдавать список, появится count: 0, 1, 42. Вот здесь важно, чтобы это были числа, а не строки — иначе клиенту (хоть Postman-скрипту, хоть приложению) придётся делать лишние преобразования.

Самый наглядный пример того, почему тип важен, — сортировка. Строки сортируются как текст, а числа — как числа. В текстовом мире "10" «меньше» "2", потому что '1' идёт раньше '2'. В числовом мире 10 больше 2. И если вы когда-нибудь увидите странную сортировку или фильтрацию, первая мысль должна быть: «А мы точно не превратили числа в строки?».

Кстати, если вы работаете с JSON глазами (в Postman или просто читая ответ), вы почти всегда можете отличить строку от числа одним движением: у строки будут кавычки, у числа — нет.

4. Boolean в JSON: true/false без кавычек

Булевы значения в JSON — это маленькие честные флажки. Они идеальны для полей вида «включено/выключено», «доступно/недоступно», «есть/нет». Их проще всего читать глазами, и именно поэтому их иногда чаще всего портят… «улучшениями». Например, кто-то решает написать "True" с большой буквы, кто-то — "yes", кто-то — "0" и "1". И вот тут уже начинается цирк, который клиенты вынуждены разбирать руками.

В JSON всё строго: булево значение — это только true или false, обязательно маленькими буквами, и без кавычек.

Мини-набор примеров:

// Настоящие boolean в JSON: только true/false и только без кавычек
String jsonTrue = "true";
String jsonFalse = "false";

// А вот это уже строка, хотя выглядит как "похоже на false"
String jsonTextFalse = "\"false\"";

System.out.println(jsonTrue);      // true
System.out.println(jsonFalse);     // false
System.out.println(jsonTextFalse); // "false"

Третий пример — это не boolean, а строка со словом false. Для человека может быть «ну смысл-то понятен», но для контракта это уже другой тип, и клиент, который ожидает boolean, будет вправе сказать: «Извините, я это есть не буду».

Где в ReadLater Starter могут появиться boolean? В нашем учебном домене их немного, но они вполне возможны как служебные флаги. Например, в /health (который будет позже) можно теоретически иметь healthy: true. Или во внешнем каталоге у книги может быть hasCover: true. Неважно, какое именно поле — важно, что как только мы выбираем boolean, мы придерживаемся настоящего boolean, а не «псевдобулева текста».

Ещё один нюанс: boolean в JSON — это именно логический тип, а не «техническая экономия символов». Поэтому идеи «давайте хранить флаги как 0/1» чаще всего ухудшают читабельность и добавляют конвертацию на стороне клиента.

5. null в JSON: значения нет

null — это, пожалуй, самое полезное и самое неправильно понятое слово в JSON. Новички часто воспринимают его как «пусто» в широком смысле: пустая строка, ноль, отсутствие поля — всё туда же. Но на уровне API-контракта null означает очень конкретную вещь: значение отсутствует явно. То есть мы не «забыли» прислать поле, а сознательно говорим: «поле есть, но значения у него сейчас нет».

В JSON null пишется так и только так: null, без кавычек.

// null в JSON — отдельное значение, без кавычек
String jsonNull = "null";
System.out.println(jsonNull); // null

В реальных API null особенно часто встречается в опциональных полях. Например, в ReadLater у книги может не быть externalId (если мы добавили её вручную, а не из каталога), или пользователь мог не оставить комментарий. Тогда встречаются такие фрагменты:

// Текстовый блок: удобно показывать JSON без экранирования кавычек в Java-строке
String jsonCommentNull = """
{ "comment": null }
""";

String jsonExternalIdNull = """
{ "externalId": null }
""";

Я специально показываю это внутри объекта, чтобы вы увидели «естественный» контекст, но в этой лекции нам не нужно разбирать сам объект — только значение null.

Важно не перепутать null с «пустой строкой» и «нулём». Если поле "comment": "", это означает: комментарий есть, он текстовый, но пустой. Если "count": 0, это означает: значение есть, оно числовое, и это ноль. Если "comment": null, это означает: комментария как значения нет (или он неизвестен, или не задан — и это должен объяснять контракт).

И да, я знаю, что мозг просит «ну это же всё по смыслу почти одно и то же». В API это не так. Клиент (и вы сами через пару недель) благодарит контракт за точность, даже если сначала хочется попроще.

Совсем чуть-чуть забросим «крючок» на следующую лекцию дня: null — это одно состояние, но есть ещё состояние «поле отсутствует». Это другое. Мы подробно сравним их позже, но уже сейчас полезно держать в голове: null — это явное отсутствие значения.

6. Визуальный парсер по первым символам

Когда JSON становится длинным и вложенным, очень хочется «схватить смысл» за секунду: где строка, где число, где объект, где массив. Хорошая новость: JSON в этом смысле дружелюбен. Тип значения почти всегда угадывается по первым символам, если вы смотрите не на смысл слов, а на форму записи. Это один из тех маленьких навыков, который внезапно делает чтение API-ответов спокойным.

Самая простая эвристика такая: посмотрите на первый «значимый» символ (после пробелов и переносов). Если это " — перед вами строка. Если это цифра или - — скорее всего число. Если это t или f — boolean. Если nnull. Если { — объект. Если [ — массив.

Можно даже сделать маленькую «памятку» в виде таблицы:

Первый символ (после пробелов) Тип значения
" строка
0…9 или
-
число
t /
f
true / false
n null
{ объект (следующая лекция)
[ массив (следующая лекция)

Если хочется закрепить это на уровне кода (просто как иллюстрацию, а не как настоящий парсер), можно написать мини-функцию «угадай тип по виду» — и увидеть, что логика действительно элементарная:

static String guessJsonType(String json) {
    // Убираем пробелы/переносы: нам важен первый "значимый" символ
    String t = json.trim();

    // Строка в JSON всегда начинается с двойной кавычки
    if (t.startsWith("\"")) return "STRING";

    // Boolean — только true/false (без кавычек)
    if (t.equals("true") || t.equals("false")) return "BOOLEAN";

    // null — отдельное значение (без кавычек)
    if (t.equals("null")) return "NULL";

    // По первым символам легко распознать объект/массив
    if (t.startsWith("{")) return "OBJECT";
    if (t.startsWith("[")) return "ARRAY";

    // Если это не похоже ни на что выше, чаще всего это число (но тут мы его не валидируем)
    return "NUMBER (или что-то странное)";
}

Эта функция не делает валидацию и не понимает нюансы чисел — и нам это сейчас не нужно. Она показывает главное: JSON построен так, чтобы тип «читался» из формы, а не угадывался по контексту.

7. Типы и API-контракт

На уровне «читаю глазами» типы кажутся мелочью. Но backend-разработка быстро учит: контракт существует не для красоты, а для того, чтобы две независимые стороны могли надежно взаимодействовать. Клиент не сидит у вас в голове и не угадывает, что вы «имели в виду». Он видит конкретные байты и пытается превратить их в структуру данных. И если тип “уехал”, начинается цепочка неприятностей.

Представьте типичный сценарий для нашего будущего локального API: вы отдаёте список reading list items и рядом пишете count. Если вы вернули count как строку ("count": "2"), то клиент, который ожидает число, либо упадёт с ошибкой, либо вынужден будет делать конвертацию. В Postman это тоже видно мгновенно: кавычки вокруг числа — как красный флажок «мы сейчас договорились о разных вещах».

Или другой сценарий: статус чтения. Если вы вернули "status": "FINISHED" — это строка, и клиент может сравнивать её с ожидаемыми вариантами. Если вы вернули "status": true (да, такое тоже бывает в реальной жизни, когда кто-то «оптимизировал» модель), то клиент не сможет понять, что это означает: прочитано? Не прочитано? А где тогда IN_PROGRESS? То есть проблема не только в типе, но и в смысле, который тип помогает удержать.

Ещё один частый источник боли — null. Для опциональных полей comment или externalId null — нормальное состояние, но только если оно ожидаемо по контракту. Если клиент думает, что там всегда строка, а сервер иногда присылает null, появляются NPE-образные истории (в разных языках это проявляется по-разному, но суть одна: «я ожидал текст, а получил отсутствие значения»).

Поэтому правило «видеть тип» — это не теория ради теории. Это практическая защита от ситуации, когда вы вроде бы «отдаёте правильные данные», но система разваливается из-за того, что данные оказались другого типа. И что особенно важно для нас в этом курсе: пока мы не используем никакие фреймворки, нам нужно научиться самим замечать такие проблемы глазами, ещё до того, как появятся удобные DTO и автоматические маппинги.

8. Типичные ошибки с простыми значениями

Ошибки с простыми JSON-значениями обычно коварны тем, что выглядят «почти правильно». Мозг дорисовывает смысл, и кажется, что всё окей — пока вы не попробуете отдать это в реальный клиент, не откроете ответ в Postman или не попросите программу разобрать данные. Ниже — самые частые грабли, на которые наступают новички, и причины, почему эти грабли такие популярные.

Ошибка №1: одинарные кавычки вместо двойных в строках.
Запись вида 'Clean Code' часто выглядит “ну почти же то самое”. Но для JSON это уже не строковое значение, а невалидный фрагмент. Как только в строках появляются одинарные кавычки вместо двойных, клиенту уже нечего разбирать без костылей.

Ошибка №2: числа в кавычках “на всякий случай”.
Очень частая мысль: «Ну пусть будет строкой, так надёжнее». В результате 2008 превращается в "2008", count превращается в "0", и клиент внезапно не может делать числовые операции без конвертации. Это ломает сортировки, фильтры и просто добавляет трение. Если значение числовое по смыслу контракта, оно должно быть числом и по форме.

Ошибка №3: булевы значения как строки — "true" и "false".
Это почти всегда происходит из-за логики «я вижу слово, значит это строка». Но в JSON boolean — отдельный тип, и false отличается от "false". Если вы отправите "false" вместо false, клиент может воспринять это как «непустая строка», а значит «истина» в некоторых языках/проверках, и получится очень весёлый баг: «у меня false, но всё равно работает как true».

Ошибка №4: десятичная запятая вместо точки в числах.
3,14 — привычно человеку, но не JSON. JSON использует точку: 3.14. Запятая в JSON — это разделитель элементов, а не часть числа. Поэтому запись с запятой делает документ невалидным или приводит к совершенно другому разбору.

Ошибка №5: null воспринимают как пустую строку, ноль или “ложь”.
null — это отдельное значение, которое означает «значения нет». Пустая строка "" означает «строка есть, но пустая». Ноль 0 означает «число есть и равно нулю». false означает «логическое значение есть и ложно». Если смешивать эти состояния, контракт становится туманным: клиенту приходится угадывать, что именно вы хотели сказать.

1
Задача
Java Server, 10 уровень, 1 лекция
Недоступна
Определение типа простого JSON-значения
Определение типа простого JSON-значения
1
Задача
Java Server, 10 уровень, 1 лекция
Недоступна
Подсчет простых JSON-типов из файла
Подсчет простых JSON-типов из файла
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ