Обновление библиотеки до седьмой версии я откладывал полгода, пока не понадобился чужой пример с фильтрами — он собирался только на v7. После обновления компилятор выдал список предупреждений на сотню строк: мой скетч 2019 года, три форка с GitHub и половина закладок писались в другие эпохи. Примеры в интернете старше версий — это нормально: шестая вышла в 2018-м, седьмая — весной 2024-го, а туториалы живут дольше обеих. Глупость ситуации в том, что чужой код v5 и v6 вам всё равно придётся читать: он окружает нас в форумах, чатах и готовых проектах, значит — нужен навык перевода, а не только новый синтаксис.
Главный сдвиг седьмой версии формулируется в одно предложение: пул памяти всегда живёт в куче, поэтому StaticJsonDocument и DynamicJsonDocument слились в один JsonDocument. В шестой разница между ними была в источнике байтов: Static вырезал ёмкость из статической области ещё на компиляции, Dynamic брал её у кучи malloc-ом в момент создания и был чуть медленнее. Седьмая план не спрашивает: не хватило — просит у кучи ещё порцию и продолжает разбор. На практике декларация перестала быть обещанием плате и стала обычным объявлением переменной, которое ничего не резервирует.
Разберём три отличия по существу, переезд за три шага и отдельный случай — примеры пятой версии, где поменялись не имена, а сам ход разбора. Базу седьмой версии мы уже проходили в уроке про JsonDocument и в уроке про установку; здесь — сравнение поколений и практический перенос старого кода.
Три отличия по существу
Первое — декларация. Два класса с ёмкостью превращаются в один без неё: StaticJsonDocument<256> doc; и DynamicJsonDocument doc(256); в седьмой версии пишутся одинаково — JsonDocument doc;. Старые имена не выкинуты: они остались псевдонимами, старый код собирается, но каждая декларация даёт предупреждение о deprecated. Я перестал глушить эти предупреждения и начал их собирать: список warning-строк после первой компиляции — готовый чек-лист переезда, бесплатно.
Второе — строки. Шестая версия хранила const char* по указателю: строка лежала там, где лежала, и никто её не копировал — мы разбирали это в уроке про строки и копии вместе с граблями срока жизни. Седьмая всегда делает копию в пул: указатель не может протухнуть, за это платят байтами ёмкости — на маленьких документах цена копеечная, на длинных заголовках заметная. Приём с strdup и ручными копиями из отдельного урока в седьмой версии теряет смысл — пул держит строки сам. На бумаге это плата за спокойствие; в цифрах — те самые байты, которые мы научимся считать в конце серии.
Третье — смысл кода NoMemory. В шестой он значил: ёмкость мала, увеличьте число в скобках. В седьмой ёмкости нет, и тот же код значит: кончилась куча — вопрос переезжает с декларации на весь бюджет платы. Проверка err из урока про коды разбора не меняется ни буквой; меняется лечение — вместо цифры в скобках разбираются с кучей. Проверка err при этом одна и та же, только список подозреваемых другой: не декларация, а соседние библиотеки, String-буферы и сам размер пакета.
Переезд в большинстве случаев механический. Шаг первый — декларации: имя, минус размер. Шаг второй — лишние doc.clear(): deserializeJson сбрасывает документ перед разбором сам, и заготовка clear из старых примеров стала шумом — это проверялось ещё в уроке про JsonDocument седьмой версии. Шаг третий — пересборка со списком предупреждений: каждая deprecated-строка — точка правки, и правятся они однообразно. Исключение — проекты с несколькими файлами: компилятор покажет предупреждения по всем сразу, и список выйдет длиннее, чем вы ожидали, — это нормально, а не повод для правок вслепую. Переезд не считается законченным после компиляции: у меня есть контрольный прогон — разбираю тот же пакет, что и до обновления, и печатаю measureJson рядом со старой цифрой; длины печати совпадают до байта, а на сборке своего документа флаг overflowed() ловит тихий пропуск, который раньше находили по обрубку пакета в журнале сервера.
Примеры пятой версии правятся по смыслу. Там разбор выглядел как JsonObject& root = jsonBuffer.parseObject(packet); — буфер отдавал ссылку на объект, отдельного кода разбора не было вовсе, а неудача проверялась методом root.success(). Печать звалась root.printTo(Serial), замер длины — root.measureLength(). Словарик соответствий — в таблице ниже; он закрывает примерно весь интернет до 2018 года, где примеры на v5 до сих пор гадают ёмкость наугад.
Ниже — один и тот же разбор в трёх редакциях: тело скетча на седьмой версии, шестая и пятая — в комментариях рядом. Так скетч читается как параллельный перевод: правка видна строчка к строчке, и видно, что логика переезду не подчинилась — она её не заметила.
#include <ArduinoJson.h> String packet = "{\"id\":7,\"name\":\"nasos\",\"on\":true,\"power\":1450}"; JsonDocument doc; // v6: StaticJsonDocument<256> doc; // v5: StaticJsonBuffer<256> jsonBuffer; void setup() { Serial.begin(9600); DeserializationError err = deserializeJson(doc, packet); // v5: JsonObject& root = jsonBuffer.parseObject(packet); // if (!root.success()) { ... } if (err) { Serial.print("razbor sorvan: "); Serial.println(err.c_str()); return; } int id = doc["id"]; // одинаково с v6 const char* name = doc["name"]; // v7 скопирует строку в пул, v6 держал указатель bool on = doc["on"]; int power = doc["power"]; Serial.print("id="); Serial.println(id); Serial.print("name="); Serial.println(name); Serial.print("on="); Serial.println(on); Serial.print("power="); Serial.println(power); serializeJson(doc, Serial); // v6: serializeJson тоже; v5: root.printTo(Serial); Serial.println();} void loop() {}Словарик переезда
| Было (v5 / v6) | Стало (v7) | Замечание |
|---|---|---|
| StaticJsonBuffer<200> / DynamicJsonBuffer | JsonDocument doc; | Пятая: буфер отдавал JsonObject&, седьмая — сам документ |
| JsonObject& root = buffer.parseObject(s); | deserializeJson(doc, s); | Пятая: разбор без кода, проверка через success() |
| root["k"].success() | doc.containsKey("k") | Пятая: флаг у варианта; седьмая: метод документа |
| StaticJsonDocument<256> / DynamicJsonDocument(256) | JsonDocument doc; | Шестая: ёмкость обязательна; седьмая: пул растёт сам |
Поколения пакетов в симуляторе
Движок книги понимает оба написания — и JsonDocument без ёмкости, и DynamicJsonDocument(768) в духе старых примеров, — поэтому в сим-скетче ниже оба документа живут рядом: старым разбирается пакет 2023 года, новым — пакет 2025-го. Честная оговорка: на настоящей плате седьмой версии первая строка даст предупреждение о deprecated и дальше будет работать как обычный пул; в симуляторе предупреждений не видно, разницу показывает только компилятор. measureJson при этом печатает одни и те же байты для обоих документов — пул один, различие только в том, кто его планировал.
Пакеты — два поколения одного сервиса: в 2023-м были temp и humi, к 2025-му сервер добавил rain и press. Код чтения doc.temp и doc.humi не изменился ни на символ: смена версии библиотеки и смена протокола — независимые события, и оба не трогают чтение. LED на D12 горит, когда в пакете найдено поле rain: containsKey различает поколения пакетов тем же API, каким вы читаете значения. Сравните bayt двух пакетов: печать потяжелела на два десятка байта — серверы растут вширь без спроса, и именно печать показывает это первым.
- В v7 StaticJsonDocument и DynamicJsonDocument слились в один JsonDocument: пул всегда в куче и растёт порциями, старые имена — псевдонимы с предупреждением
- Строки в v7 всегда копируются в пул: указатель не протухнет, цена — байты ёмкости; strdup-приёмы шестой версии становятся не нужны
- NoMemory в v7 значит «кончилась куча», а не «мала ёмкость»: лечение переезжает с декларации на бюджет всей платы
- Переезд за три шага: декларации, лишние clear, разбор deprecated-списка; логика и чтения не меняются
- Примеры v5 правятся по смыслу: parseObject и printTo превратились в deserializeJson и serializeJson, success() — в containsKey
- 1. Что случилось со StaticJsonDocument и DynamicJsonDocument в седьмой версии?
- 2. Шестая версия хранила const char* из ответа по указателю. Что делает седьмая?
- 3. Скетч на v6 ловит NoMemory с DynamicJsonDocument(256). Как это лечится после переезда на v7?
- 4. Перед вами строка из примера 2017 года: JsonObject& root = jsonBuffer.parseObject(packet);. Что здесь главное поменять?
- 5. Сервер добавил в пакет поле rain. Сломается ли код чтения doc.temp после обновления библиотеки до v7?
После переезда остаётся вопрос, который седьмая версия не решила, а переадресовала: сколько памяти вообще просить под пакет, когда куча на Uno — два килобайта. План больше не нужен декларации, но нужен вам — при расчёте бюджета кучи, при диагностике NoMemory и при подборе фильтров. Считаем по слотам и строкам, с проверкой печати и прикидкой на салфетке — следующий урок: NoMemory и расчёт ёмкости. Домашнее задание: найдите в закладках самый старый пример с JSON, переведите его по словарику и сравните, сколько предупреждений осталось после пересборки — обычно ноль, а если не ноль, то это честный список того, что вы ещё не трогали. Остаток обычно живёт в забытых модулях — одна-две строки, и это хороший исход: список короче, чем память проблем. Попутно сравните печатные размеры старых и новых пакетов — то же упражнение готовит вас к расчёту ёмкости из следующего урока.
Частые вопросы по теме урока
- Что случилось со StaticJsonDocument и DynamicJsonDocument в седьмой версии?
- Пул седьмой версии всегда живёт в куче, различие Static и Dynamic потеряло смысл. Старые имена собираются с предупреждением о deprecated — это бесплатный чек-лист переезда. Слили в один JsonDocument, имена остались псевдонимами с предупреждением
- Шестая версия хранила const char* из ответа по указателю. Что делает седьмая?
- В седьмой версии строки всегда копируются в пул: указатель не может протухнуть после освобождения буфера приёма. На маленьких документах цена копеечная, на длинных заголовках — заметная. Копирует строку в пул, платя байтами ёмкости
- Скетч на v6 ловит NoMemory с DynamicJsonDocument(256). Как это лечится после переезда на v7?
- Ёмкости в седьмой версии нет, и код указывает на кончившуюся кучу: соседние String, буферы библиотек и сам размер пакета. Цифру в скобках менять нечего — её больше не существует. Разобраться с кучей: в v7 код значит, что свободной памяти нет
- Перед вами строка из примера 2017 года: JsonObject& root = jsonBuffer.parseObject(packet);. Что здесь главное поменять?
- JsonBuffer, parseObject и ссылки на объект остались в пятой версии. Седьмая разбирает в сам документ и возвращает код DeserializationError — правка по смыслу, а не по имени. Перейти на deserializeJson: буферы пятой версии больше не существуют
- Сервер добавил в пакет поле rain. Сломается ли код чтения doc.temp после обновления библиотеки до v7?
- Чтение doc.temp работает одинаково на любом поколении пакетов и любой версии библиотеки. Новое поле — вопрос протокола, и его ловит containsKey, когда оно вам нужно. Нет: смена версии и смена протокола — независимые события