Project Overview & Hardware Spec Sheet
When building dynamic IoT systems, hardcoding GPIO configurations is a dead end. You need a way to pass a list of hardware configurations at runtime. This is where parsing Arduino JsonArray members becomes critical. In this guide, we will build a dynamic GPIO configurator that reads a JSON array of relay states, validates the pins against a hardware whitelist, and sets the outputs accordingly.
Parts List
- Microcontroller: ESP32 DevKit V1 (38-pin NodeMCU-32S variant) — $6.00 - $9.00
- Logic Level Converter: TXS0108E 8-channel bi-directional module — $2.50
- Relays: 3x Songle SRD-05VDC-SL-C 5V active-low relay modules — $4.00 each
- Wiring: 22 AWG solid core jumper wires, 1x half-size breadboard
Pin Mapping Table
The ESP32 operates at 3.3V logic, while the Songle relay modules require 5V logic to trigger reliably without chattering. We use the TXS0108E to shift the levels. Bench note: Ensure the OE (Output Enable) pin on the TXS0108E is tied directly to 3.3V, or the outputs will float.
| ESP32 GPIO | TXS0108E (LV Side) | TXS0108E (HV Side) | Relay Module | Function |
|---|---|---|---|---|
| GPIO 16 | A1 | B1 | IN1 | Pump Control |
| GPIO 17 | A2 | B2 | IN2 | Valve Control |
| GPIO 18 | A3 | B3 | IN3 | Heater Control |
| 3V3 | VCCA & OE | - | - | Low Voltage Ref |
| 5V (VIN) | - | VCCB | VCC | High Voltage Ref |
Understanding Arduino JsonArray Members vs. Elements
A common point of confusion in the ArduinoJson V7 documentation is the terminology. Strictly speaking, a JsonArray contains elements (indexed by position), while a JsonObject contains members (key-value pairs). When makers search for "Arduino JsonArray members," they are almost always trying to iterate through an array of objects and access the object members nested inside each array element.
For example, in the payload [{"pin": 16, "state": "HIGH"}], the entire object {"pin": 16, "state": "HIGH"} is the element at index 0. The key "pin" and its value 16 are the members of that object. ArduinoJson V7 handles this elegantly with range-based for loops, automatically casting the element to a JsonObject so you can extract the members directly.
DynamicJsonDocument(2048), ArduinoJson V7 uses a JsonDocument that automatically allocates memory from the heap in blocks. This prevents the classic ESP32 stack overflow crashes when parsing large arrays of sensor configurations.
Complete ESP32 Code: Parsing Array Members
This code targets the ESP32 DevKit V1. It defines a hardware whitelist to prevent the JSON payload from accidentally configuring strapping pins (like GPIO 0 or GPIO 2) which would cause boot failures.
#include <ArduinoJson.h>
#include <cstring>
// Target: ESP32 DevKit V1 (NodeMCU-32S 38-pin variant)
// Hardware: 3x Songle SRD-05VDC-SL-C Relays via TXS0108E Logic Level Converter
#define MAX_CONFIGURABLE_PINS 4
const int ALLOWED_PINS[MAX_CONFIGURABLE_PINS] = {16, 17, 18, 19};
const char* json_config = R"([
{"id": 1, "pin": 16, "state": "HIGH", "label": "Pump"},
{"id": 2, "pin": 17, "state": "LOW", "label": "Valve"},
{"id": 3, "pin": 18, "state": "HIGH", "label": "Heater"},
{"id": 4, "pin": 2, "state": "HIGH", "label": "BadPin"}
])";
bool isPinAllowed(int pin) {
for (int i = 0; i < MAX_CONFIGURABLE_PINS; i++) {
if (ALLOWED_PINS[i] == pin) return true;
}
return false;
}
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println(F("Initializing GPIO Configurator..."));
// V7 Syntax: JsonDocument replaces DynamicJsonDocument
JsonDocument doc;
DeserializationError error = deserializeJson(doc, json_config);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
JsonArray configArray = doc.as<JsonArray>();
// Iterating through array elements to access nested object members
for (JsonObject element : configArray) {
int id = element["id"];
int pin = element["pin"];
const char* stateStr = element["state"];
const char* label = element["label"];
// Safety check: Do not configure ESP32 strapping pins
if (!isPinAllowed(pin)) {
Serial.printf("[REJECTED] ID %d (%s): GPIO %d is not in the allowed hardware map.\n", id, label, pin);
continue;
}
pinMode(pin, OUTPUT);
int stateVal = (strcmp(stateStr, "HIGH") == 0) ? HIGH : LOW;
digitalWrite(pin, stateVal);
Serial.printf("[OK] Configured ID %d (%s) on GPIO %d to %s\n", id, label, pin, stateStr);
}
}
void loop() {
// Static configuration loaded in setup.
// Add your main application logic here.
delay(1000);
}
Debugging: Exact Error Strings & Ranked Causes
When working with Arduino JsonArray members, compilation and runtime errors are common, especially when migrating from older tutorials. If your build fails, look for these exact strings.
Error 1: error: 'class ArduinoJson::V720PB2::JsonArray' has no member named 'createNestedObject'
Ranked Causes:
- V6 to V7 API Migration: You are using ArduinoJson V7, but following a V6 tutorial. V7 removed
createNestedObject(). - Fix: Replace
array.createNestedObject()witharray.add<JsonObject>().
Error 2: error: 'DynamicJsonDocument' was not declared in this scope
Ranked Causes:
- Library Version Mismatch:
DynamicJsonDocumentwas deprecated and removed in V7. - Fix: Change
DynamicJsonDocument doc(2048);to simplyJsonDocument doc;.
The First Three Things to Check When Parsing Fails
If the code compiles but deserializeJson() returns an error at runtime, check these immediately:
- Trailing Commas: JSON strictly forbids trailing commas in arrays or objects (e.g.,
[{"pin": 16},]). Run your payload through a linter like JSONLint. - String Escaping: If reading from an SD card or HTTP, ensure internal quotes are escaped properly. Using raw string literals
R"(...)"in C++ avoids this during testing. - Memory Fragmentation: While V7 handles memory better, parsing a massive 50KB array on an ESP32 without PSRAM can still fail with
NoMemory. If your array is huge, ensure you are using an ESP32-WROVER module with external PSRAM and configure ArduinoJson to use it.
Extending and Simplifying the Build
How to Extend: To make this a true IoT device, replace the hardcoded json_config string with an HTTP GET request using the ESP32's HTTPClient library. You can stream the JSON directly into the JsonDocument using deserializeJson(doc, http.getStream()). This avoids loading the entire payload into RAM twice, a critical optimization for ESP32 memory-constrained environments.
How to Simplify: If you just want to test the parsing logic without wiring up relays and logic level converters, strip out the pinMode and digitalWrite calls. Keep the Serial.printf statements to verify that the array elements and object members are being extracted correctly. You can run this simplified version on any Arduino Uno or Nano by changing the serial baud rate to 9600.
FAQ: Arduino JsonArray Members
How do I count the number of members in an Arduino JsonArray?
Technically, arrays have elements, not members. To count the number of elements in a JsonArray, use the size() method: int count = configArray.size();. If you need to count the key-value pairs (members) inside a specific object within that array, you cast the element to a JsonObject and call size() on that object.
Can I modify JsonArray members in place without copying?
Yes. ArduinoJson V7 uses reference semantics. When you iterate through the array using for (JsonObject element : configArray), element is a reference to the actual data inside the JsonDocument. If you execute element["state"] = "LOW";, you are modifying the underlying document directly, not a copy. This saves significant RAM on microcontrollers.
Why does iterating over JsonArray members crash my ESP32?
The most common cause of an ESP32 crash (Guru Meditation Error / Stack Overflow) during iteration is attempting to access a member that doesn't exist, which returns a null variant, and then passing that null variant to a function that expects a strict type (like strcmp). Always use element["key"].isNull() or provide default values using the pipe operator: const char* state = element["state"] | "LOW"; to prevent null pointer dereferences.
What is the difference between JsonArray elements and JsonObject members?
An element is a value inside an array, accessed by a zero-based integer index (e.g., array[0]). A member is a key-value pair inside an object, accessed by a string key (e.g., object["pin"]). When you have an array of objects, you first select the element by index, then access the member by key: array[0]["pin"].






