К сожалению, для версии 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>();
Думаю, разобраться с примерами кода будет несложно. Если возникнут замечания и предложения — пишите. 🙂