В прошлом сезоне я собирал узел учёта тепла для котельной, и он присылал в Serial-монитор строку вида {"node":"kotelnaya","t":21.4,"ok":true}. Полчаса я смотрел на неё как на заклинание: двойные кавычки через слово, двоеточия, запятые, скобки в два этажа. Прочитать глазами — можно. Ответить на вопрос «а что будет, если сервер пошлёт поля в другом порядке» — уже нет. Тогда я ещё не знал главного: передо мной не текст, а структура, у которой есть грамматика, и правил в этой грамматике меньше десятка.
Выучить эти правила стоит до открытия документации любой библиотеки. Девять из десяти вопросов на форумах по ArduinoJson — не про библиотеку, а про сам JSON: человек путает массив с объектом, пишет температуру в кавычках и удивляется, что Temp и temp — разные ключи. Библиотека тут бессильна: она честно разбирает то, что ей дали, вместе с вашими опечатками. Для человека эта разметка — шум, для машины — точная карта, где лежит каждое значение; выигрывает тот, кто научился смотреть на неё глазами парсера.
План урока: две пары скобок и четыре типа значений, экранирование и три главные ловушки, массивы и вложенность — со шпаргалкой-таблицей под конец. Разбирать будем настоящий deserializeJson прямо в симуляторе: движок книги поддерживает его так же, как плату. Такие ответы мы уже видели вживую, когда гоняли HTTP-запросы на ESP32, — теперь разберём их по косточкам.
Две пары скобок и четыре типа значений
JSON строится из двух контейнеров. Объект — фигурные скобки, внутри пары «ключ: значение» через запятую: {"node":"kotelnaya","t":21.4}. Ключ — всегда строка в двойных кавычках, без исключений. Значение — любого из четырёх сортов: число, строка, true или false, null. Число пишется как есть: целое 21 или с точкой 21.4 — но не с запятой. Запись 21,4 для русского глаза естественна, а парсер прочтёт её как два значения подряд — и разбор сломается на ровном месте, без единого внятного сообщения.
Второй контейнер — массив: квадратные скобки и значения через запятую, например [18, 20, 22]. Массив — список без имён: порядок важен, первый элемент сидит под индексом ноль. Внутри массива могут лежать и объекты: прогноз на неделю — это массив из семи объектов, каждый с датой и температурой. Матрёшка собирается свободно: объект внутри массива внутри объекта — сколько нужно, столько и вкладывайте, парсер не устаёт.
Тип парсер выводит из записи, а не из догадок: началось с цифры — число, с кавычки — строка, true/false — булево. Отсюда правило, которое бьёт по рукам чаще всего: не оборачивайте числа в кавычки. "t":"21.4" — это строка из четырёх символов, а не температура: сравнение t > 20 на плате сработает не так, как вы ждёте, потому что строка и число сравниваются по-разному. Мы это проходили в уроке про типы переменных: тип решает, как значение хранится и как ведёт себя в выражениях.
И три свойства синтаксиса, которые экономят нервы. Пробелы и переносы строк не значат ничего: сервер вправе прислать всё одной строкой или с красивыми отступами — парсеру всё равно, двоеточие с пробелами и без он читает одинаково. Порядок полей не значим: узел, который сегодня шлёт температуру первой, завтра может послать её последней, и это не ошибка. А вот регистр ключей значим: Temp и temp — два разных ключа, и опечатка в регистре даст пустое значение, не сказав ни слова.
{ "node": "kotelnaya", "temp": 21.4, "humi": 58, "ok": true, "forecast": [20, 21, 19], "sensor": { "id": "dht22", "ok": true }}Экранирование и три главные ловушки
Внутри строки двойная кавычка — служебный символ, поэтому её пишут с обратным слешем: \". Точно так же экранируется сам слеш (двойным слешем), перенос строки (\n) и табуляция (\t). Отсюда колючий вид плотных ответов — кавычки с палочками. Экранирование честное: при разборе библиотека превращает \" обратно в кавычку, и вы получаете чистый текст без палочек. Увидели в ответе одиночный слеш перед знаком — это не мусор и не ошибка сервера, это экран, за которым прячется служебный символ.
Ловушка первая — одинарные кавычки. {'t': 21} выглядит похоже, но это не JSON: ключ обязан быть в двойных кавычках. Парсер вернёт ошибку InvalidInput, документ останется пустым, а скетч, если проверку не написал, молча прочитает нули. Ловушка вторая — запятая после последнего элемента: {"t":21,} — привычка из JavaScript, где такой хвост прощают; в JSON за ним следует ошибка разбора. Ловушка третья — комментарии: ни //, ни /* */ в JSON не существуют, какой бы удобной ни казалась строчка-пояснение в конфиге.
Ловушка четвёртая коварнее всех — мусор перед скобкой. Сервер ответил ошибкой 401 и HTML-страницей, где слово error и разметка, а скетч честно скормил это deserializeJson. Первая же буква — ошибка, потому что документ обязан начинаться с { или . Лекарство простое: проверяйте код HTTP-ответа до разбора, а не после — на ESP32 мы это правило соблюдали при работе с [погодными API через клиент, и оно ни разу не подвело.
Массивы и вложенность: сколько скобок нужно ардуинщику
Массив выручает, когда значений много и они однородные. Прогноз по дням — три числа в квадратных скобках вместо трёх ключей day1, day2, day3: короче, и циклом читается. На настоящей плате доступ по индексу выглядит как doc["forecast"][0] — ноль, первый, второй элемент. В сим-строках этого урока массивов не будет: движок читает плоские ключи, и я честно упрощаю пакет до поддержанного — на плате же массивы работают в полную силу, и в уроке про парсинг с ArduinoJson мы их трогали.
Вложенный объект — способ спрятать связанные поля в одну ветку. В примере выше датчик живёт в своей скобке: id и ok лежат внутри sensor, и на плате читаются цепочкой doc["sensor"]["id"]. Красиво, но каждый уровень вложенности — накладные расходы в документе: память под промежуточный объект плюс время разбора. Моё правило: на Uno держите глубину два-три уровня, дальше — либо плоские ключи, либо переезд на ESP32, где сотни килобайт ОЗУ позволяют не считать этажи.
Прежде чем парсить чужой ответ, научитесь читать его глазами: скопируйте в любой онлайн-форматировщик JSON — он разложит плотную строку на уровни с отступами, и матрёшка станет видна за секунду. А в своём протоколе держите привычку оборачивать пакет маркерами начала и конца: в уроках про протоколы по проводу мы читали строки до символа-терминатора — с JSON это работает так же, потому что пакет обязан начинаться с фигурной скобки и ею же заканчиваться.
Где вы встретите всё это вживую: погодные API кладут в массив дни или часы, MQTT-брокеры гоняют вложенные состояния устройств, а конфиги на SD-карте — объект с настройками, который вы сами же и писали: разбор конфига с SD строится ровно на этом синтаксисе. Скобки везде одни и те же — один раз выучили, работает везде.
| Элемент | Как выглядит | Что хранит |
|---|---|---|
| Объект | {"node":"kotelnaya"} | Пары «ключ: значение» в фигурных скобках; ключи уникальны |
| Массив | [20, 21, 19] | Список значений; порядок важен, индексация с нуля |
| Строка | "kotelnaya" | Текст в двойных кавычках; кавычка внутри экранируется слешем |
| Число | 21.4 | Целое или с точкой; запятая — разделитель пар, не десятичный знак |
| Булево | true | Флаг без кавычек; второй вариант — false |
| Null | null | Пустое значение; при чтении в число превращается в ноль |
- Порядок не важен. Переставьте поля в строке ответа: temp первым, node последним — значения те же самые. Сервер делает так же, без предупреждения.
- Число против строки. Замените 21.4 на "21.4" и посмотрите на печать serializeJson в хвосте вывода: ключ temp станет в кавычки — тип виден прямо в JSON, хотя числа совпадают.
- Молчаливый ноль. Строка wind= печатает ноль: ключа wind в пакете нет, и чтение по нему даёт пустоту без единой жалобы — вот почему разбор и наличие ключа на плате проверяют явно.
- Сломайте пакет сам. Уберите одну кавычку после kotelnaya и запустите: разбор сорвётся, и вы своими глазами увидите, как выглядит пустой документ.
- JSON — два контейнера: объект в фигурных скобках с парами «ключ: значение» и массив в квадратных; ключ — всегда строка в двойных кавычках
- Четыре типа значений: число без кавычек (с точкой, не с запятой), строка, true/false, null; тип выводится из записи
- Пробелы, переносы и порядок полей не значим; регистр ключей значим: Temp и temp — разные ключи
- Кавычка внутри строки экранируется слешем; одинарные кавычки, хвостовая запятая и комментарии — невалидный JSON
- Чтение по отсутствующему ключу даёт ноль молча — проверяйте разбор и наличие ключей, не полагайтесь на удачу
- Глубина вложенности стоит памяти: на Uno держите два-три уровня, плоские ключи дешевле матрёшки
- 1. Какой символ в JSON разделяет целую и дробную часть числа?
- 2. Какая из записей — невалидный JSON?
- 3. Ключа "humi" в документе нет. Что даст чтение doc["humi"] при приведении к числу, если ошибку не проверять?
- 4. Сервер прислал поля в другом порядке и с лишними пробелами. Что изменится при разборе?
Домашнее задание без платы: найдите три источника JSON вокруг себя — ответ любого погодного сервиса в браузере, конфиг в домашнем сервере, сообщение из MQTT-топика — и прочитайте их глазами: где объект, где массив, какие типы у значений, где прячется экранированная кавычка. Прогоните каждый через онлайн-форматировщик: пять минут такой практики стоят недели чтения про синтаксис. Умение видеть структуру за колючими кавычками — половина навыка, и вторая половина — инструменты: в следующем уроке ставим библиотеку ArduinoJson через менеджер, сравниваем шестую версию с седьмой и выясняем, почему из старых туториалов торчит StaticJsonDocument — установка и отличия версии 7.
Частые вопросы по теме урока
- Какой символ в JSON разделяет целую и дробную часть числа?
- Только точка: 21.4. Запятая в JSON — разделитель пар и элементов, поэтому 21,4 парсер прочтёт как два значения и сломается. Точка
- Какая из записей — невалидный JSON?
- Строки в JSON живут только в двойных кавычках. Одинарные — из JavaScript, в JSON за ними ошибка InvalidInput. ['a', 'b']
- Ключа "humi" в документе нет. Что даст чтение doc["humi"] при приведении к числу, если ошибку не проверять?
- Отсутствующий ключ даёт null-прокси: в числе это ноль, в строке пустота. Никаких сообщений — поэтому проверки обязательны. Ноль — молча
- Сервер прислал поля в другом порядке и с лишними пробелами. Что изменится при разборе?
- Порядок полей и пробелы в JSON не несут смысла. Парсер читает структуру по именам ключей, а не по позициям символов. Ничего: порядок и пробелы не значимы