Инструкции

JSON на Arduino и ESP32: библиотека ArduinoJson 7

11 мин чтения·Август 2026

JSON — стандартный способ обменяться структурированными данными с браузером, сервером или соседним устройством. На микроконтроллере его почти всегда делают библиотекой ArduinoJson. Главное, что нужно знать перед началом: в седьмой версии API заметно изменился, а интернет по-прежнему полон примеров под шестую.

Что изменилось в версии 7

Раньше нужно было заранее считать, сколько памяти займёт документ, и выбирать между двумя классами. Теперь класс один, и размер он подбирает сам:

// Было в версии 6
StaticJsonDocument<256> doc;
DynamicJsonDocument doc(256);

// Стало в версии 7
JsonDocument doc;
ℹ️

Старый код при этом чаще всего собирается: StaticJsonDocument и DynamicJsonDocument в седьмой версии сохранены как совместимые обёртки над JsonDocument и помечены устаревшими — вы увидите предупреждения компилятора, но не ошибки. Ломается не объявление документа, а те методы, которые убрали совсем.

А убрали действительно: макросы JSON_OBJECT_SIZE() и JSON_ARRAY_SIZE(), методы capacity(), memoryUsage(), garbageCollect(), shallowCopy(), containsKey(), а также createNestedObject() и createNestedArray(). Вот на них старый пример и остановится.

ЗадачаВерсия 6Версия 7
ДокументStaticJsonDocument<N>JsonDocument
Массив по ключуdoc.createNestedArray("data")doc["data"].to<JsonArray>()
Объект по ключуdoc.createNestedObject("info")doc["info"].to<JsonObject>()
Объект в массивarray.createNestedObject()array.add<JsonObject>()
Проверка наличия ключаdoc.containsKey("a")doc["a"].is<int>()
Проверка переполненияпо capacity()doc.overflowed()

Мнемоника простая: to<T>() — по ключу, заменяет значение; add<T>() — в массив, добавляет элемент.

⚠️

Ещё одно изменение ломает код тихо, без ошибки компиляции: serializeJson() теперь заменяет содержимое строки, а не дописывает в конец. Строчки output = "" перед вызовом больше не нужны — а если код полагался на дописывание, поведение изменится.

Собрать JSON

#include <ArduinoJson.h>

void setup() {
  Serial.begin(115200);

  JsonDocument doc;

  doc["device"] = "roboarm";
  doc["uptime"] = millis() / 1000;

  JsonArray angles = doc["angles"].to<JsonArray>();
  angles.add(90);
  angles.add(45);
  angles.add(0);

  JsonObject status = doc["status"].to<JsonObject>();
  status["connected"] = true;
  status["temperature"] = 36.6;

  serializeJson(doc, Serial);
  Serial.println();
}

void loop() {
}

Результат:

{"device":"roboarm","uptime":12,"angles":[90,45,0],"status":{"connected":true,"temperature":36.6}}

Куда писать результат — определяет второй аргумент. Serial пишет прямо в порт, String — в строку, буфер char[] — в массив:

String payload;
serializeJson(doc, payload);

char buffer[256];
serializeJson(doc, buffer, sizeof(buffer));

Есть и serializeJsonPretty() — с отступами и переносами. Читать удобнее, но объём вырастает в полтора-два раза, так что для обмена данными берут обычный вариант.

Разобрать JSON

const char* json =
    "{\"device\":\"roboarm\",\"angles\":[90,45,0],\"status\":{\"connected\":true}}";

JsonDocument doc;
DeserializationError error = deserializeJson(doc, json);

if (error) {
  Serial.print("Не удалось разобрать JSON: ");
  Serial.println(error.f_str());
  return;
}

const char* device = doc["device"];
int firstAngle = doc["angles"][0];
bool connected = doc["status"]["connected"];

Serial.print(device);
Serial.print(", первый угол ");
Serial.print(firstAngle);
Serial.print(", связь ");
Serial.println(connected);

Проверка ошибки обязательна. DeserializationError приводится к bool, где истина означает сбой, а f_str() даёт текстовое описание — например, IncompleteInput, если строка оборвалась, или NoMemory, если данные не поместились.

Обращение к отсутствующему ключу не приводит к падению: вернётся нулевое значение для запрошенного типа. Если важно отличить «ноль» от «ключа нет», проверяйте тип:

if (doc["angle"].is<int>()) {
  int angle = doc["angle"];
}

Перебор массивов и объектов

for (JsonVariant value : doc["angles"].as<JsonArray>()) {
  Serial.println(value.as<int>());
}

for (JsonPair item : doc["status"].as<JsonObject>()) {
  Serial.print(item.key().c_str());
  Serial.print(" = ");
  Serial.println(item.value().as<String>());
}

Чтение прямо из ответа сервера

deserializeJson() принимает не только строку, но и любой поток. Это позволяет разбирать ответ сервера, не собирая его целиком в память:

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

void fetchWeather() {
  HTTPClient http;
  http.begin("http://api.example.com/weather");

  if (http.GET() == 200) {
    JsonDocument doc;
    DeserializationError error = deserializeJson(doc, http.getStream());

    if (!error) {
      float temperature = doc["main"]["temp"];
      Serial.printf("Температура: %.1f\n", temperature);
    }
  }

  http.end();
}
ℹ️

Сетевые потоки не буферизованы, и библиотека читает их по одному байту — на больших ответах это заметно медленно. Автор библиотеки рекомендует оборачивать поток в ReadBufferingStream из своей же библиотеки StreamUtils. Для ответов в пару килобайт разница несущественна.

Отдельный приём для больших ответов — фильтрация: попросить библиотеку сохранить только нужные поля, а остальное пропустить мимо. Это резко снижает расход памяти, когда сервер отдаёт много лишнего.

Сколько это стоит по памяти

Седьмая версия работает исключительно через динамическое выделение памяти, и на восьмибитных платах это ощутимо: по замерам автора, пример парсера на AVR стал примерно на 40% больше, чем в шестой версии.

Поэтому официальная рекомендация звучит так:

  • Arduino UNO, Nano, Mega — оставайтесь на ArduinoJson 6. Она поддерживается и работает.
  • ESP32, ESP8266, UNO R4 и другие 32-битные — берите версию 7. Там тот же прирост составляет пару процентов от общего размера прошивки, а API удобнее.

В менеджере библиотек нужную версию можно выбрать в выпадающем списке перед установкой — см. Библиотеки Arduino IDE.

⚠️

На плате с двумя килобайтами ОЗУ JSON вообще стоит применять с осторожностью: документ на десяток полей вместе с исходной строкой легко съедает половину памяти. Если обмен идёт между двумя вашими же устройствами, текстовый протокол вида go x=10 y=20 обойдётся в разы дешевле. Про расход ОЗУ — в статье Память Arduino и ESP32.

Частые проблемы

'class JsonDocument' has no member named 'createNestedObject' и подобные ошибки про containsKey, capacity или JSON_OBJECT_SIZE. Пример написан под версию 6, а установлена 7: эти методы удалены. Замены — в таблице выше. Само объявление StaticJsonDocument<256> при этом соберётся, только с предупреждением.

NoMemory при разборе. На ESP32 обычно означает, что документ действительно огромный — примените фильтрацию. На AVR это штатная ситуация: памяти не хватает.

Числа с плавающей точкой теряют знаки. По умолчанию сериализуется несколько значащих цифр. Нужную точность задают вторым аргументом: doc["value"] = serialized(String(x, 4)).

Кириллица в значениях. JSON работает с UTF-8, и библиотека передаёт байты как есть. Проблемы обычно на стороне получателя, а не платы.

Что дальше

Связанные статьи