Вступление

JSON сейчас встречается буквально везде - от веб-сервисов до IoT, но есть нюанс: почти все популярные JSON-библиотеки писались с расчётом на десктопы и серверы, где оперативку никто не считает по килобайтам. А вот на микроконтроллерах, особенно Cortex-M, каждый байт на счету. Да, конечно, можно гонять сырые структуры и их же писать в конфиг-файл, это как говорится "не запрещено конституцией". Но отладка в таком случае превращается в отдельный квест. В какой-то момент я понял, что мне надоело вручную возиться с JSON на микроконтроллерах: писать километры кода для обхода дерева cJSON, ловить утечки и гадать, где malloc снова подставит. Из всех этих соображений и родился JsonX — лёгкая и минималистичная надстройка над cJSON, которую я делал в первую очередь под микроконтроллеры.

Преимущество JsonX:

  • удобный маппинг JSON <> C-структуры через плоское описание JX_ELEMENT[]

  • возможность работать полностью без динамических аллокаций (baremetal) или через системные аллокаторы (ThreadX / FreeRTOS)

  • простая конфигурация через jx_config.h

  • и, самое важное, простая сериализация / десериализация C-структур

Основная идея JsonX

Хотя cJSON внутри парсит JSON в классическое дерево указателей, JsonX дополняет этот процесс плоским массивом элементов JX_ELEMENT[], который используется для автоматического маппинга на поля вашей структуры. Схема данных JX_ELEMENT[] описывается плоско и заранее.

typedef struct
{
  char                    property[JX_PROPERTY_MAX_SIZE];
  JX_ELEMENT_TYPE         type;
  uint8_t                 value_len;
  const void              value_p;
  JX_ELEMENT_STATUS       status;
  struct json_element_s   element;
  uint16_t                element_size;
} JX_ELEMENT;

Это нам даёт:
Контролируемая работа с памятью: хотя cJSON внутри всё ещё строит дерево и делает аллокации, JsonX изолирует их в заданном аллокаторе и гарантирует корректное освобождение без утечек.

Пример использования

Структура конфигурации:

typedef struct
{
  char      device_name[32];
  uint32_t  baudrate;
  bool      debug;
} config_t;

config_t config;

Описание маппинга:

JX_ELEMENT config_desc[] =
{
  JX_PROPERTY_STRING("device_name", config.device_name),
  JX_PROPERTY_NUMBER("baudrate", config.baudrate),
  JX_PROPERTY_BOOLEAN("debug", config.debug),
};

const size_t config_desc_size = sizeof(config_desc)/sizeof(config_desc[0]);

Парсинг JSON:

const char *config_json_str = "{\"device_name\":\"sensor1\",\"baudrate\":9600,\"debug\":true}";

if(jx_json_to_struct(char *buffer, config_desc, config_desc_size, JX_MODE_STRICT) == JX_SUCCESS)
{
  jx_log("Конфиг загружен: %s @ %lu\n", config.device_name, config.baudrate);
}

Генерация JSON:

char buffer[256];
jx_struct_to_json(config_desc, config_desc_size, buffer, sizeof(buffer), JX_FORMATTED);
jx_log("config: %s\r\n", buffer);

Гибкая конфигурация под проект

JsonX можно собрать в одном из нескольких режимов:

  • ThreadX / FreeRTOS - с использованием системных аллокаторов

  • Baremetal - с собственным статическим пулом

  • Custom Allocators - использование полностью пользовательских функций выделения/освобождения памяти, переданных в JsonX (например, для POSIX или специфичных RTOS-аллокаторов)

Всё настраивается через jx_config.h и флаги компиляции.

Ограничения

  • Все числа - пока double (как у cJSON). Планируется режим strict int

  • Максимальный размер имени поля задаётся макросом JX_PROPERTY_MAX_SIZE

  • Нет автоматического увеличения массива элементов - лимит задаётся заранее.

Планы на развитие

В текущей версии JsonX использует cJSON как движок парсинга и генерации. Однако в перспективе я рассматриваю вариант полного отказа от cJSON в пользу собственного парсера, который:

  • будет работать напрямую с заданным пулом памяти (например, block pool в ThreadX)

  • получит безопасный аллокатор для FreeRTOS с контролем границ и предотвращением фрагментации

  • сможет работать в полностью статическом режиме без единого вызова malloc/free

  • Снижение накладных расходов и позволит полностью исключить динамику при работе с JSON, если это критично для проекта

Окончательное решение будет зависеть от того, насколько библиотека будет востребована в сообществе и какие требования будут у пользователей. Если спрос на такую функциональность окажется высоким - развитие пойдёт в эту сторону

Заключение

JsonX - это способ использовать cJSON в микроконтроллерах без непредсказуемых аллокаций и лишнего кода. Он особенно полезен там, где важно контролировать каждый байт и каждую миллисекунду, но при этом нужно работать с JSON так же удобно, как на ПК.

Репозиторий: JsonX
Примеры - в файле: src/example.c

Комментарии (0)


  1. nikolz
    18.09.2025 12:31

    Что скажите про эти библиотеки:

    --------------------------------------

    Рекомендации DeepSeek :

    1. ArduinoJson (самая популярная)

    • Плюсы: Легковесная, простая в использовании, отличная документация

    • Поддерживаемые архитектуры: AVR, ARM, ESP8266, ESP32

    • Особенности: Поддержка как десериализации, так и сериализации

    cpp

    #include <ArduinoJson.h>
    
    void setup() {
      StaticJsonDocument<200> doc;
      doc["sensor"] = "temperature";
      doc["value"] = 23.5;
      
      serializeJson(doc, Serial);
    }

    2. jansson (порт для embedded)

    • Плюсы: Полнофункциональная, стандартный API

    • Минусы: Требует больше памяти

    • Лучше подходит для: Более мощных микроконтроллеров (ESP32, STM32)

    3. cJSON

    • Плюсы: Минималистичная, переносимая, только C

    • Размер: ~10KB ROM

    • Поддержка: ARM, AVR, ESP

    #include "cJSON.h"
    
    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "name", "test");
    cJSON_AddNumberToObject(root, "value", 42);

    4. JsonParser

    • Плюсы: Очень легкая, только парсинг

    • Размер: ~2-4KB ROM

    • Лучше для: Только чтения JSON

    5. Microjson (минималистичная)

    • Плюсы: Крайне малый размер, только парсинг

    • Размер: ~1.5KB ROM

    Рекомендации по выбору:

    • Для AVR/ATmega: ArduinoJson или Microjson

    • Для ESP8266/ESP32: ArduinoJson или cJSON

    • Для STM32: ArduinoJson или jansson

    • Если нужно только парсить: JsonParser или Microjson

    ArduinoJson обычно是最好的 выбор для большинства проектов благодаря балансу функциональности и эффективности использования памяти.

    =============================

    Рекомендации GLM:

    Ниже представлен обзор популярных библиотек для работы с JSON на микроконтроллерах с учётом их особенностей и ограничений по ресурсам:

    1. ArduinoJson (рекомендуется для Arduino/ESP)

    • Платформы: Arduino, ESP8266, ESP32, STM32 и др.

    • Особенности:

      • Оптимизировано для устройств с ограниченным объёмом памяти (ОЗУ/Флэш).

      • Поддерживает сериализацию (создание

      • Удобный API, интеграция с Arduino IDE

      • Автоматическое управление памятью (статический или динамический пул).

    • Пример:

      #include <ArduinoJson.h>

      void setup() {

      StaticJsonDocument<200> doc; // Выделяем 200 байт

      doc["sensor"] = "temperature";

      doc["value"] = 25.5;

      serializeJson(doc, Serial); // Вывод: {"sensor":"temperature","value":25.5}

      }

    • Ссылка: [arduinojson.org](https://arduino

    2. cJSON (лёгкая библиотека на C)

    • Платформы: любые микроконтроллеры (STM32, AVR, ESP32 и др.).

    • Особенности:

      • Минималистичная (один файл: cJSON.c + cJSON.h).

      • Не требует динамической памяти (но может использовать malloc).

      • Поддерживает парсинг и генерацию JSON.

      • Низкое потребление RAM.

    • Пример:

      #include "cJSON.h"

      void parse_json(const char *json) {

      cJSON *root = cJSON_Parse(json);

      cJSON *value = cJSON_GetObjectItem(root, "value");

      printf("Value: %f\n", value->valuedouble);

      cJSON_Delete(root);

      }

    • Ссылка: GitHub — DaveGamble/cJSON

    3. JSMN (минималистичный парсер на C)

    • Платформы: устройства с крайне ограниченным объёмом памяти (ATtiny, MSP430).

    • Особенности:

      • Только парсинг (без генерации JSON).

      • Не использует malloc (работает с буфером).

      • Очень маленький размер кода (~1 КБ).

      • Требует ручной обработки токенов.

    • Пример:

      #include "jsmn.h"

      void parse_json(const char *json) {

      jsmn_parser parser;

      jsmntok_t tokens[10];

      jsmn_init(&parser);

      int count = jsmn_parse(&parser, json, strlen(json), tokens, 10);

      // Обработка токенов вручную...

      }

    • Ссылка: GitHub — zserge/jsmn

    4. Mongoose OS (сетевая библиотека с поддержкой JSON)

    • Платформы: ESP32, ESP8266, STM32.

    • Особенности:

      • Включает парсер JSON как часть сетевой библиотеки.

      • Поддержка MQTT, HTTP, WebSocket.

      • Требует больше ресурсов, чем cJSON/JSMN.

    • Пример:

      #include "mgos.h"

      void handler(struct mg_connection nc, int ev, void ev_data) {

      struct mg_http_message hm = (struct mg_http_message ) ev_data;

      double value = json_get_double(hm->body, "$.value");

      printf("Value: %f\n", value);

      }

    • Ссылка: cesanta.com/mongoose

    5. nlohmann/json (для мощных микроконтроллеров)

    • Платформы: ESP32, STM32H7, Teensy 4.x.

    • Особенности:

      • Современный C++ API (удобный, но ресурсоёмкий).

      • Требуется C++11 и поддержка STL.

      • Не рекомендуется для маломощных микроконтроллеров (из-за высокого потребления оперативной памяти).

    • Пример:

      #include <nlohmann/json.hpp>

      void parse_json(const std::string &json) {

      auto doc = nlohmann::json::parse(json);

      float value = doc["value"];

      Serial.println(value);

      }

    • Ссылка: GitHub — nlohmann/json

    6. PicoJSON (лёгкая альтернатива nlohmann)

    • Платформы: ESP32, STM32.

    • Особенности:

      • Компакт

      • Поддерживает парсинг и генерацию.

      • Меньше ресурсов, чем у nlohmann/json

    • Ссылка: [GitHub — kazuho/picojson](GitHub — kazuho/picojson

    Рекомендации по выбору:

    1. Для Arduino/ESP: ArduinoJson (идеальное сочетание удобства и ресурсов).

    2. Для маломощных МК (ATtiny, AVR): JSMN (только парсинг) или cJSON.

    3. Для STM32/ESP32 без Arduino: cJSON (C) или PicoJSON (C++).

    4. Для сетевых проектов: Mongoose OS (если нужна интеграция с MQTT/HTTP).

    5. Для мощных микроконтроллеров (ESP32, Teensy 4.x): nlohmann/json (если хватает оперативной памяти).

    Все библиотеки, кроме nlohmann/json, подходят для устройств с оперативной памятью от 2 КБ. Для проектов с критичным потреблением памяти выбирайте JSMN или cJSON.


    1. mihailasd Автор
      18.09.2025 12:31

      Спасибо за ваш интерес и детальный обзор других JSON-библиотек.
      Да, большинство библиотек из списка я знаю и пробовал. ArduinoJson, JSMN, jansson - все они хороши в своих нишах, ArduinoJson например действительно очень удобна на Arduino/ESP, у нее шикарная документация и примеры.
      Но JsonX решает чуть другую задачу:

      1. не пытается конкурировать с ArduinoJson или jansson, а именно дополняет cJSON.

      2. Я делал JsonX в первую очередь для проектов на STM32 + RTOS, где хотелось иметь жесткий контроль над памятью (пул ThreadX, кастомные аллокаторы для FreeRTOS, статический буфер в baremetal). Это даёт предсказуемость и стабильность.

      3. Многие популярные JSON-библиотеки делают ставку на C++ и естественно используют кучу. В C++ реализовать маппинг JSON структура относительно просто: есть шаблоны, STL и прочая "тяжёлая артиллерия".
        А вот в чистом C это боль и грабли с топором на конце. Если библиотека написана на C, то: как правило, она ограничивается только парсингом JSON. Дальше разработчику приходится вручную обходить дерево или токены и самому переносить данные в структуру. JsonX решает эту проблему: он даёт именно автоматический маппинг JSON <> C-структуры через JX_ELEMENT[]. То есть схема описывается один раз, а дальше можно работать напрямую со структурами, без километров шаблонного кода.


  1. zurabob
    18.09.2025 12:31

    1. JSMN (минималистичный парсер на C)

    У меня подозрение, что автор вдохновлялся именно им, я тоже к нему добавлял парсинг чисел, true/false и генерацию json. Очень приятная и небольшая заготовка для самодеятельности.


    1. mihailasd Автор
      18.09.2025 12:31

      Да, JSMN тоже нравится, но в JsonX я сразу хотел уйти дальше простого парсинга и добавить маппинг структур + контроль памяти.