You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
22 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

[![latest](https://img.shields.io/github/v/release/GyverLibs/GSON.svg?color=brightgreen)](https://github.com/GyverLibs/GSON/releases/latest/download/GSON.zip)
[![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/)
[![Foo](https://img.shields.io/badge/%E2%82%BD$%E2%82%AC%20%D0%9D%D0%B0%20%D0%BF%D0%B8%D0%B2%D0%BE-%D1%81%20%D1%80%D1%8B%D0%B1%D0%BA%D0%BE%D0%B9-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/)
[![Foo](https://img.shields.io/badge/README-ENGLISH-blueviolet.svg?style=flat-square)](https://github-com.translate.goog/GyverLibs/GSON?_x_tr_sl=ru&_x_tr_tl=en)
[![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social&logo=telegram&color=blue)](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
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код