[![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
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код