К сожалению, для версии ArduinoJSON v.6, которая пока в бета-версии, очень мало примеров работы. Работа с ней отличается от V.5. Из-за скудной документации мне потребовалось некоорое время, чтобы разобраться, поэтому задокументирую свои находки.
В качестве примера возьму JSON файл конфигурации для Modbus.
[
{
"Slave": {
"Connection": "ttyS0",
"HwId": "TermoSensor-0a:01:01:01:01:02",
"BaudRate": "9600",
"DataBits": "8",
"StopBits": "1",
"Parity": "ODD",
"FlowControl": "NONE",
"Ops": [
{
"PollingInterval": "2000",
"UnitId": "1",
"Function": "0x04",
"Address": "0x01",
"Len": "1",
"DisplayName": "Temp"
},
{
"PollingInterval": "2000",
"UnitId": "1",
"Function": "0x04",
"Address": "0x02",
"Len": "1",
"DisplayName": "Humidity"
}
]
}
},
{
"Slave": {
"Connection": "192.168.1.2",
"TcpPort": "502",
"HwId": "TermoSensor-0a:01:01:01:01:02",
"Ops": [
{
"PollingInterval": "2000",
"UnitId": "1",
"Function": "0x04",
"Address": "0x01",
"Len": "1",
"DisplayName": "Temp"
}
]
}
}
]Удобно визуализировать файл с помощью online JSON редактора.

Чтобы вычитать массив из файла нужно использовать следующий код.
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#define Num_of_Serials 2 //Number of serial ports on the board
#define Num_of_Ops 5 //Number of operations per connection
const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Serials*JSON_OBJECT_SIZE(8) + 830;
StaticJsonDocument<capacity> doc;
char* rs485cfg = "/rs485cfg.txt";
void autoProcess(String json)
{
//DynamicJsonDocument doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
processError(error.code());
return;
}
JsonObject obj = doc.as<JsonObject>();
JsonArray arr = doc.as<JsonArray>();
/*Serial.println("--------------------------------------");
JsonArray::iterator it;
for (it=arr.begin(); it!=arr.end(); ++it) {
const JsonObject& elem = *it;
JsonObject slave = elem["Slave"];
printValue("Connection", slave["Connection"].as<String>());
}*/
Serial.println("--------------------------------------");
for (const JsonObject& item : arr) {
const JsonObject& slaves = item["Slave"];
//printValue("Connection", slave["Connection"].as<String>());
for (const JsonPair& slave : slaves) {
String key = slave.key().c_str();
if (slave.value().is<JsonArray>())
{
JsonArray operations = slave.value().as<JsonArray>();
Serial.println("---Operations:-------------------------");
for (const JsonObject& operation : operations) {
for (const JsonPair& op : operation) {
key = op.key().c_str();
Serial.println("\t" + key + ": " + op.value().as<String>());
}
Serial.println("--------------------------------------");
}
}
else
{
Serial.println(key + ": " + slave.value().as<String>());
}
}
}
}
void processError(int error)
{
switch (error) {
case DeserializationError::Ok:
Serial.print(F("Deserialization succeeded"));
break;
case DeserializationError::InvalidInput:
Serial.print(F("Invalid input!"));
break;
case DeserializationError::NoMemory:
Serial.print(F("Not enough memory"));
break;
default:
Serial.print(F("Deserialization failed"));
break;
}
return;
}Сразу отмечу, что можно использовать DynamicJsonDocument:
DynamicJsonDocument doc(capacity); vs StaticJsonDocument<capacity> doc;
В примере используется StaticJsonDocument. Я не разобрался до конца с формулой для расчета capacity, поэтому дал ориентировочный вариант с приличным запасом. Уточню формулу позже. У ArduinoJSON есть сервис Assistant. Им можно воспользоваться для получения нужного размера. К сожалению, если JSON файл используется для конфигурирования, не известно сколько объектов в нем будет, поэтому приходится использовать некоторые предельные значения. Естественно, это понапрасну отъедает память. 🙁
Динамический вариант парсинга JSON массива интересен, но в случае конфигурационных файлов полезнее ручной вариант задания типов с перегрузкой в необходимые массивы структур или объектов. Рассмотрю вариант с динамическими массивами структур для размещения значений файла конфигурации протокола Modbus после парсинга JSON array.
typedef struct{
int PollingInterval;
int UnitId;
int Function;
int Address;
int Len;
String DisplayName;
} Operation;
typedef std::vector<Operation> OperationsType;
typedef struct{
String Connection;
String HwId;
int BaudRate;
int DataBits;
int StopBits;
String Parity;
String FlowControl;
int TcpPort;
OperationsType Operations;
} Slave;
typedef std::vector<Slave> Slaves;
void manualProcess(String json)
{
Slaves slaves;
//DynamicJsonDocument doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
processError(error.code());
return;
}
JsonObject obj = doc.as<JsonObject>();
JsonArray arr = doc.as<JsonArray>();
//Serial.println("--------------------------------------");
for (int i=0; i<arr.size(); i++) {
JsonObject slave = arr[i]["Slave"];
Slave slaveItem;
slaveItem.Connection = slave["Connection"].as<String>();
slaveItem.HwId = slave["HwId"].as<String>();
slaveItem.BaudRate = slave["BaudRate"].as<int>();
slaveItem.DataBits = slave["DataBits"].as<int>();
slaveItem.StopBits = slave["StopBits"].as<int>();
slaveItem.Parity = slave["Parity"].as<String>();
slaveItem.FlowControl = slave["FlowControl"].as<String>();
slaveItem.TcpPort = slave["TcpPort"].as<int>();
printValue("Connection", slaveItem.Connection);
printValue("HwId", slaveItem.HwId);
printValue("BaudRate", String(slaveItem.BaudRate));
printValue("DataBits", String(slaveItem.DataBits));
printValue("StopBits", String(slaveItem.StopBits));
printValue("Parity", slaveItem.Parity);
printValue("FlowControl", slaveItem.FlowControl);
printValue("TcpPort", String(slaveItem.TcpPort));
JsonArray ops = arr[i]["Slave"]["Ops"];
Operation operation;
Serial.println("---Operations:-------------------------");
for (int i=0; i<ops.size(); i++) {
JsonObject op = ops[i];
operation.PollingInterval = op["PollingInterval"].as<int>();
operation.UnitId = op["UnitId"].as<int>();
const char* function = op["Function"].as<char*>();
operation.Function = StrToHex(function);//op["Function"].as<int>();
const char* address = op["Address"].as<char*>();
operation.Address = StrToHex(address);//op["Address"].as<int>();
operation.Len = op["Len"].as<int>();
operation.DisplayName = op["DisplayName"].as<String>();
slaveItem.Operations.push_back(operation);
printValue("\tPollingInterval", String(operation.PollingInterval));
printValue("\tUnitId", String(operation.UnitId));
printValue("\tFunction", String(operation.Function));
printValue("\tAddress", String(operation.Address));
printValue("\tLen", String(operation.Len));
printValue("\tDisplayName", operation.DisplayName);
Serial.println("--------------------------------------");
}
slaves.push_back(slaveItem);
}
}
int StrToHex(const char* str)
{
if (strchr(str, 'x') != NULL )
{
return (int) strtol(str, 0, 16);
}
return (int) strtol(str, 0, 10);
}
void printValue(String name, String value)
{
if ((value != "null") && (value != "") && (value != "0")) //exclude 0 - it's not so good idea. :-)
{
Serial.println(name + ": " + value);
}
}В примере создается динамический массив структур с использованием шаблона vector.
typedef std::vector<Slave> Slaves; typedef std::vector<Operation> OperationsType;
Поскольку в JSON нет информации о нужном типе, приведение приходится делать вручную:
slaveItem.StopBits = slave["StopBits"].as<int>(); slaveItem.Parity = slave["Parity"].as<String>();
Думаю, разобраться с примерами кода будет несложно. Если возникнут замечания и предложения — пишите. 🙂