|
|
[](https://github.com/GyverLibs/GSON/releases/latest/download/GSON.zip)
|
|
|
[](https://alexgyver.ru/)
|
|
|
[](https://alexgyver.ru/support_alex/)
|
|
|
[](https://github-com.translate.goog/GyverLibs/GSON?_x_tr_sl=ru&_x_tr_tl=en)
|
|
|
|
|
|
[](https://t.me/GyverLibs)
|
|
|
|
|
|
# GSON
|
|
|
Парсер и сборщик данных в формате JSON для Arduino
|
|
|
- В 6 раз быстрее и сильно легче ArduinoJSON
|
|
|
- Парсинг JSON с обработкой ошибок
|
|
|
- Линейная сборка JSON-пакета
|
|
|
- Экранирование "опасных" символов
|
|
|
- Работает на базе AnyText ([StringUtils](https://github.com/GyverLibs/StringUtils))
|
|
|
- Работает с 64 битными числами
|
|
|
- Встроенный механизм хэширования ключей
|
|
|
- *Библиотека не подходит для хранения и изменения данных! Только парсинг и сборка пакетов*
|
|
|
|
|
|
### Совместимость
|
|
|
Совместима со всеми Arduino платформами (используются Arduino-функции)
|
|
|
|
|
|
### Зависимости
|
|
|
- Библиотека [StringUtils](https://github.com/GyverLibs/StringUtils)
|
|
|
|
|
|
## Содержание
|
|
|
- [Документация](#docs)
|
|
|
- [Использование](#usage)
|
|
|
- [Версии](#versions)
|
|
|
- [Установка](#install)
|
|
|
- [Баги и обратная связь](#feedback)
|
|
|
|
|
|
<a id="docs"></a>
|
|
|
|
|
|
## Документация
|
|
|
### `AnyText`
|
|
|
Под типом `AnyText` понимается строка в любом формате:
|
|
|
- `"const char"` - строки
|
|
|
- `char[]` - строки
|
|
|
- `F("f-строки")`
|
|
|
- `String` - строки
|
|
|
|
|
|
### `gson::Parser`
|
|
|
```cpp
|
|
|
// конструктор
|
|
|
gson::Parser();
|
|
|
gson::Parser(размер);
|
|
|
gson::ParserStatic<размер>();
|
|
|
|
|
|
// методы
|
|
|
uint16_t length(); // получить количество элементов
|
|
|
uint16_t size(); // получить размер документа в оперативной памяти (байт)
|
|
|
void hashKeys(); // хешировать ключи всех элементов (операция необратима)
|
|
|
bool hashed(); // проверка были ли хешированы ключи
|
|
|
|
|
|
Entry get(AnyText key); // доступ по ключу (главный контейнер - Object)
|
|
|
Entry get(size_t hash); // доступ по хэшу ключа (главный контейнер - Object)
|
|
|
Entry get(int index); // доступ по индексу (главный контейнер - Array или Object)
|
|
|
|
|
|
AnyText key(int idx); // прочитать ключ по индексу
|
|
|
size_t keyHash(int idx); // прочитать хэш ключа по индексу
|
|
|
AnyText value(int idx); // прочитать значение по индексу
|
|
|
int8_t parent(int idx); // прочитать родителя по индексу
|
|
|
Type type(int idx); // получить тип по индексу
|
|
|
|
|
|
const __FlashStringHelper* readType(uint16_t idx); // прочитать тип по индексу
|
|
|
|
|
|
// парсить. Вернёт true при успешном парсинге. Можно указать макс. уровень вложенности
|
|
|
bool parse(String& json, uint8_t maxdepth = 16);
|
|
|
bool parse(const char* json, uint8_t maxdepth = 16);
|
|
|
|
|
|
// вывести в Print с форматированием
|
|
|
void stringify(Print* p);
|
|
|
|
|
|
// обработка ошибок
|
|
|
bool hasError(); // есть ошибка парсинга
|
|
|
Error getError(); // получить ошибку
|
|
|
uint16_t errorIndex(); // индекс места ошибки в строке
|
|
|
const __FlashStringHelper* readError(); // прочитать ошибку
|
|
|
```
|
|
|
#### Настройки
|
|
|
Объявляются перед подключением библиотеки
|
|
|
```cpp
|
|
|
// отключить поддержку хэширования ключей (экономит 4 байта на элемент при парсинге)
|
|
|
#define GSON_NO_HASH
|
|
|
```
|
|
|
|
|
|
#### Лимиты
|
|
|
- После парсинга один элемент весит 8 (10 с хэшем) байт на AVR и 8 (12 с хэшем) байт на ESP
|
|
|
- Максимальное количество элементов: 255 на AVR и 512 на ESP
|
|
|
- Максимальная длина json-строки: 65 536 символов
|
|
|
- Максимальная длина ключа: 32 символа
|
|
|
- Максимальная длина значения: 32768 символов
|
|
|
- Максимальный уровень вложенности элементов задаётся в функции `parse()`. Парсинг - рекурсивный, каждый уровень добавляет несколько байт в оперативку
|
|
|
|
|
|
### Тесты
|
|
|
Тестировал на ESP8266, пакет сообщений из телеграм бота - 3600 символов, 147 "элементов". Получал значение самого отдалённого и вложенного элемента, в GSON - через хэш. Результат:
|
|
|
|
|
|
| Либа | Flash | SRAM | FreeHeap | Parse | Get |
|
|
|
| ----------- | ------ | ----- | -------- | -------- | ------ |
|
|
|
| ArduinoJson | 297433 | 31628 | 41104 | 10686 us | 299 us |
|
|
|
| GSON | 279349 | 31400 | 43224 | 1744 us | 296 us |
|
|
|
|
|
|
Таким образом GSON **в 6 раз быстрее** при парсинге, значения элементов получает с такой же скоростью. Сама библиотека легче на 18 кБ во Flash и 2.1 кБ в Heap. В других тестах (AVR) на получение значения GSON с хэшем работал в среднем в 1.5 раза быстрее.
|
|
|
|
|
|
### `gson::Type`
|
|
|
```cpp
|
|
|
None
|
|
|
Object
|
|
|
Array
|
|
|
String
|
|
|
Int
|
|
|
Float
|
|
|
Bool
|
|
|
```
|
|
|
|
|
|
### `gson::Error`
|
|
|
```cpp
|
|
|
None
|
|
|
Alloc
|
|
|
TooDeep
|
|
|
NoParent
|
|
|
NotContainer
|
|
|
UnexComma
|
|
|
UnexColon
|
|
|
UnexToken
|
|
|
UnexQuotes
|
|
|
UnexOpen
|
|
|
UnexClose
|
|
|
UnknownToken
|
|
|
BrokenToken
|
|
|
BrokenString
|
|
|
BrokenContainer
|
|
|
EmptyKey
|
|
|
IndexOverflow
|
|
|
LongPacket
|
|
|
LongKey
|
|
|
EmptyString
|
|
|
```
|
|
|
|
|
|
### `gson::Entry`
|
|
|
Также наследует всё из `AnyText`, документация [здесь](https://github.com/GyverLibs/StringUtils?tab=readme-ov-file#anytext)
|
|
|
|
|
|
```cpp
|
|
|
Entry get(AnyText key); // получить элемент по ключу
|
|
|
bool includes(AnyText key); // содержит элемент с указанным ключом
|
|
|
|
|
|
Entry get(size_t hash); // получить элемент по хэшу ключа
|
|
|
bool includes(size_t hash); // содержит элемент с указанным хэшем ключа
|
|
|
|
|
|
Entry get(int index); // получить элемент по индексу
|
|
|
|
|
|
bool valid(); // проверка корректности (существования)
|
|
|
uint16_t length(); // получить размер (для объектов и массивов. Для остальных 0)
|
|
|
Type type(); // получить тип элемента
|
|
|
AnyText key(); // получить ключ
|
|
|
size_t keyHash(); // получить хэш ключа
|
|
|
AnyText value(); // получить значение
|
|
|
```
|
|
|
|
|
|
### `gson::string`
|
|
|
```cpp
|
|
|
String s; // доступ к строке
|
|
|
void clear(); // очистить строку
|
|
|
bool reserve(uint16_t res); // зарезервировать строку
|
|
|
|
|
|
// прибавить gson::string. Будет добавлена запятая
|
|
|
string& add(string& str);
|
|
|
|
|
|
// добавить ключ (строка любого типа)
|
|
|
string& addKey(AnyText key);
|
|
|
|
|
|
// прибавить текст (строка любого типа) без кавычек и запятой
|
|
|
string& addText(AnyText str);
|
|
|
|
|
|
// прибавить текст (строка любого типа) без кавычек и запятой с escape символов
|
|
|
string& addTextEsc(AnyText str);
|
|
|
|
|
|
// добавить строку (строка любого типа)
|
|
|
string& addString(AnyText key, AnyText value);
|
|
|
string& addString(AnyText value);
|
|
|
string& addStringRaw(AnyText value); // без запятой
|
|
|
|
|
|
// добавить строку (строка любого типа) с escape символов
|
|
|
string& addStringEsc(AnyText key, AnyText value);
|
|
|
string& addStringEsc(AnyText value);
|
|
|
string& addStringRawEsc(AnyText value); // без запятой
|
|
|
|
|
|
// добавить bool
|
|
|
string& addBool(AnyText key, const bool& value);
|
|
|
string& addBool(const bool& value);
|
|
|
string& addBoolRaw(const bool& value); // без запятой
|
|
|
|
|
|
// добавить float
|
|
|
string& addFloat(AnyText key, const double& value, uint8_t dec = 2);
|
|
|
string& addFloat(const double& value, uint8_t dec = 2);
|
|
|
string& addFloatRaw(const double& value, uint8_t dec = 2); // без запятой
|
|
|
|
|
|
// добавить int
|
|
|
string& addInt(AnyText key, const AnyValue& value);
|
|
|
string& addInt(const AnyValue& value);
|
|
|
string& addIntRaw(const AnyValue& value); // без запятой
|
|
|
|
|
|
string& beginObj(AnyText key = ""); // начать объект
|
|
|
string& endObj(); // завершить объект
|
|
|
|
|
|
string& beginArr(AnyText key = ""); // начать массив
|
|
|
string& endArr(); // завершить массив
|
|
|
|
|
|
string& end(); // завершить пакет
|
|
|
```
|
|
|
|
|
|
<a id="usage"></a>
|
|
|
|
|
|
## Использование
|
|
|
### Парсинг
|
|
|
Библиотека **не дублирует строку** в памяти и работает с исходной строкой: запоминает позиции текста, исходную строку не меняет. Отсюда следует, что:
|
|
|
- Строка должна существовать в памяти на всём протяжении работы с json документом
|
|
|
- Если исходная строка - `String` - она категорически не должна изменяться программой до окончания работы с документом
|
|
|
|
|
|
Создание документа:
|
|
|
```cpp
|
|
|
gson::Parser p; // динамический парсер
|
|
|
gson::Parser p(10); // динамический парсер, зарезервирован под 10 элементов
|
|
|
gson::ParserStatic<10> p; // статический парсер, зарезервирован под 10 элементов
|
|
|
```
|
|
|
|
|
|
Смысл как у `String`-строки: динамический парсер будет увеличиваться в процессе парсинга в динамической памяти МК, если размер документа неизвестен. Можно заранее зарезервировать размер, чтобы парсинг происходил быстрее. Статический - выделяется статически, используя меньше памяти на слабых платформах.
|
|
|
|
|
|
```cpp
|
|
|
// получили json
|
|
|
char json[] = R"raw({"key":"value","int":12345,"obj":{"float":3.14,"bool":false},"arr":["hello",true]})raw";
|
|
|
String json = "{\"key\":\"value\",\"int\":12345,\"obj\":{\"float\":3.14,\"bool\":false},\"arr\":[\"hello\",true]};";
|
|
|
|
|
|
// парсить
|
|
|
p.parse(json);
|
|
|
|
|
|
// обработка ошибок
|
|
|
if (p.hasError()) {
|
|
|
Serial.print(p.readError());
|
|
|
Serial.print(" in ");
|
|
|
Serial.println(p.errorIndex());
|
|
|
} else Serial.println("done");
|
|
|
```
|
|
|
|
|
|
После парсинга можно вывести весь пакет с типами, ключами, значениями в виде текста и родителем:
|
|
|
```cpp
|
|
|
for (uint16_t i = 0; i < p.length(); i++) {
|
|
|
// if (p.type(i) == gson::Type::Object || p.type(i) == gson::Type::Array) continue; // пропустить контейнеры
|
|
|
Serial.print(i);
|
|
|
Serial.print(". [");
|
|
|
Serial.print(p.readType(i));
|
|
|
Serial.print("] ");
|
|
|
Serial.print(p.key(i));
|
|
|
Serial.print(":");
|
|
|
Serial.print(p.value(i));
|
|
|
Serial.print(" {");
|
|
|
Serial.print(p.parent(i));
|
|
|
Serial.println("}");
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Значения можно получать в типе `AnyText`, который может конвертироваться в другие типы и выводиться в порт:
|
|
|
- Ключом может быть строка в любом виде (`"строка"`, `F("строка")`)
|
|
|
- Можно обращаться ко вложенным объектам по ключу, а к массивам по индексу
|
|
|
|
|
|
```cpp
|
|
|
Serial.println(p["key"]); // value
|
|
|
Serial.println(p[F("int")]); // 12345
|
|
|
int val = p["int"].toInt16(); // конвертация в указанный тип
|
|
|
val = p["int"]; // авто конвертация
|
|
|
float f = p["obj"]["float"]; // вложенный объект
|
|
|
Serial.println(p["arr"][0]); // hello
|
|
|
Serial.println(p["arr"][1]); // true
|
|
|
|
|
|
// проверка типа
|
|
|
p["arr"].type() == gson::Type::Array;
|
|
|
|
|
|
// вывод содержимого массива
|
|
|
for (int i = 0; i < p["arr"].length(); i++) {
|
|
|
Serial.println(p["arr"][i]);
|
|
|
}
|
|
|
|
|
|
// а лучше - так
|
|
|
gson::Entry arr = p["arr"];
|
|
|
for (int i = 0; i < arr.length(); i++) {
|
|
|
Serial.println(arr[i]);
|
|
|
}
|
|
|
|
|
|
// Пусть json имеет вид [[123,456],["abc","def"]], тогда ко вложенным массивам можно обратиться:
|
|
|
Serial.println(p[0][0]); // 123
|
|
|
Serial.println(p[0][1]); // 456
|
|
|
Serial.println(p[1][0]); // abc
|
|
|
Serial.println(p[1][1]); // def
|
|
|
```
|
|
|
|
|
|
Каждый элемент можно вывести в тип `gson::Entry` по имени (из объекта) или индексу (из массива) и использовать отдельно, чтобы не "искать" его заново:
|
|
|
```cpp
|
|
|
gson::Entry e = p["arr"];
|
|
|
Serial.println(e.length()); // длина массива
|
|
|
Serial.println(e[0]); // hello
|
|
|
Serial.println(e[1]); // true
|
|
|
```
|
|
|
|
|
|
### Хэширование
|
|
|
GSON нативно поддерживает хэш-строки из StringUtils, работа с хэшами значительно увеличивает скорость доступа к элементам JSON документа по ключу. Строка, переданная в функцию `SH`, вообще **не существует в программе** и не занимает места: хэш считается компилятором на этапе компиляции, вместо него подставляется число. А сравнение чисел выполняется быстрее, чем сравнение строк. Для этого нужно:
|
|
|
|
|
|
1. Хэшировать ключи после парсинга:
|
|
|
```cpp
|
|
|
p.parse(json);
|
|
|
p.hashKeys();
|
|
|
```
|
|
|
|
|
|
2. Обращаться к элементам по хэшам ключей, используя функцию `sutil::SH`:
|
|
|
```cpp
|
|
|
using sutil::SH;
|
|
|
|
|
|
void foo() {
|
|
|
Serial.println(p[SH("int")]);
|
|
|
Serial.println(p[SH("obj")][SH("float")]);
|
|
|
Serial.println(p[SH("array")][0]);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
> Примечание: для доступа по хэшу используется перегрузка `[size_t]`, а для доступа к элементу массива - `[int]`. Поэтому для корректного доступа к элементам массива нужно использовать именно `int`, а не `uint8` и `uint16`! Иначе компилятор может вызвать доступ по хэшу вместо обращения к массиву.
|
|
|
|
|
|
```cpp
|
|
|
gson::Entry arr = p["arr"];
|
|
|
for (int i = 0; i < arr.length(); i++) { // счётчик int
|
|
|
Serial.println(arr[i]);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Сборка
|
|
|
JSON строка собирается **линейно** в обычную `String`-строку, что очень просто и приятно для микроконтроллера:
|
|
|
```cpp
|
|
|
gson::string gs; // создать строку
|
|
|
gs.beginObj(); // начать объект 1
|
|
|
gs.addString("str1", F("value"));// добавить строковое значение
|
|
|
gs["str2"] = "value2"; // так тоже можно
|
|
|
gs["int"] = 12345; // целочисленное
|
|
|
gs.beginObj("obj"); // вложенный объект 2
|
|
|
gs.addFloat(F("float"), 3.14); // float
|
|
|
gs["float2"] = 3.14; // или так
|
|
|
gs["bool"] = false; // Bool значение
|
|
|
gs.endObj(); // завершить объект 2
|
|
|
|
|
|
gs.beginArr("array"); // начать массив
|
|
|
gs.addFloat(3.14); // в массив - без ключа
|
|
|
gs += "text"; // добавить значение (в данном случае в массив)
|
|
|
gs += 12345; // добавить значение (в данном случае в массив)
|
|
|
gs += true; // добавить значение (в данном случае в массив)
|
|
|
gs.endArr(); // завершить массив
|
|
|
|
|
|
gs.endObj(); // завершить объект 1
|
|
|
|
|
|
gs.end(); // ЗАВЕРШИТЬ ПАКЕТ (обязательно вызывается в конце)
|
|
|
|
|
|
Serial.println(gs); // вывод в порт
|
|
|
Serial.println(gs.s); // вывод в порт (или так)
|
|
|
```
|
|
|
|
|
|
<a id="versions"></a>
|
|
|
|
|
|
## Версии
|
|
|
- v1.0
|
|
|
- v1.1 - улучшен парсер, добавлено хэширование ключей и обращение по хэш-кодам
|
|
|
- v1.2 - оптимизация под StringUtils 1.3
|
|
|
- v1.3 - оптимизация парсера, ускорение чтения значений из Parser
|
|
|
- v1.4 - оптимизация парсера, ускорение чтения, изначальная строка больше не меняется парсером
|
|
|
- v1.4.1 - поддержка ядра esp8266 v2.x
|
|
|
- v1.4.2 - добавлены Raw методы в string
|
|
|
|
|
|
<a id="install"></a>
|
|
|
|
|
|
## Установка
|
|
|
- Библиотеку можно найти по названию **GSON** и установить через менеджер библиотек в:
|
|
|
- Arduino IDE
|
|
|
- Arduino IDE v2
|
|
|
- PlatformIO
|
|
|
- [Скачать библиотеку](https://github.com/GyverLibs/GSON/archive/refs/heads/main.zip) .zip архивом для ручной установки:
|
|
|
- Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
|
|
|
- Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
|
|
|
- Распаковать и положить в *Документы/Arduino/libraries/*
|
|
|
- (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
|
|
|
- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
|
|
|
|
|
|
### Обновление
|
|
|
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
|
|
|
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
|
|
|
- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
|
|
|
|
|
|
<a id="feedback"></a>
|
|
|
## Баги и обратная связь
|
|
|
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
|
|
|
Библиотека открыта для доработки и ваших **Pull Request**'ов!
|
|
|
|
|
|
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
|
|
|
- Версия библиотеки
|
|
|
- Какой используется МК
|
|
|
- Версия SDK (для ESP)
|
|
|
- Версия Arduino IDE
|
|
|
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
|
|
|
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
|
|
|
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код |