[](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)
## Документация
### `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(); // завершить пакет
```
## Использование
### Парсинг
Библиотека **не дублирует строку** в памяти и работает с исходной строкой: запоминает позиции текста, исходную строку не меняет. Отсюда следует, что:
- Строка должна существовать в памяти на всём протяжении работы с 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); // вывод в порт (или так)
```
## Версии
- 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
## Установка
- Библиотеку можно найти по названию **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: найти библиотеку как при установке и нажать "Обновить"
- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
## Баги и обратная связь
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
Библиотека открыта для доработки и ваших **Pull Request**'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код