The Verdict: Which Arduino JSON Library to Pick
If you are pulling API data, parsing MQTT payloads, or formatting telemetry on a microcontroller, you need a JSON library that respects RAM limits and handles nested objects without crashing. The direct answer: use ArduinoJson v7 by Benoit Blanchon for 95% of ESP32 and Arduino projects. It is the undisputed industry standard for embedded C++ JSON manipulation.
Here is the decision path to confirm this is the right pick for your specific build:
| Library | Best For | Memory Model | Verdict |
|---|---|---|---|
| ArduinoJson v7 | Standard API/MQTT payloads (1KB - 16KB) | Dynamic heap allocation with automatic growth | DEFAULT PICK. Use this for ESP32, ESP8266, and Arduino Mega. |
| JsonStreamingParser | Massive payloads (>32KB) on 8-bit AVR | Event-driven streaming (zero buffer) | Choose only if parsing multi-megabyte files on an Arduino Uno. |
| Arduino_JSON (Official) | Basic Arduino Cloud integrations | Standard dynamic allocation | Skip. Lacks advanced filtering and nested object speed of ArduinoJson. |
DynamicJsonDocument and StaticJsonDocument classes have been unified in v7. You now simply declare JsonDocument doc; and it manages memory allocation automatically, scaling up to your board's heap limits.
Hardware Spec Sheet and Pin Mapping
This guide targets the ESP32-DevKitC V4 (specifically the ESP32-WROOM-32 module). The ESP32's dual-core 240MHz processor and 520KB of SRAM make it ideal for JSON serialization tasks that would choke an ATmega328P. We will parse a mock API response and render the extracted values to a local display.
Parts List
- MCU: ESP32-DevKitC V4 (ESP32-WROOM-32) — ~$6.00
- Display: 0.96" SSD1306 I2C OLED (128x64, 3.3V logic) — ~$4.50
- Wiring: 22 AWG solid core jumper wires, half-size breadboard
- Libraries Required: ArduinoJson (v7.x), Adafruit SSD1306, Adafruit GFX
Pin Mapping Table
| Component | ESP32 GPIO | Notes |
|---|---|---|
| OLED VCC | 3V3 | Do not use 5V; the SSD1306 I2C logic is strictly 3.3V. |
| OLED GND | GND | Common ground with ESP32. |
| OLED SDA | GPIO 21 | Default hardware I2C data pin on ESP32. |
| OLED SCL | GPIO 22 | Default hardware I2C clock pin on ESP32. |
| Status LED | GPIO 2 | Onboard blue LED; active HIGH. |
Step-by-Step: Parsing and Generating JSON on the ESP32
Below is the complete, compilable firmware. To isolate the JSON logic from WiFi credential debugging, this code uses a hardcoded raw JSON string simulating an OpenWeatherMap-style API response. In a production build, you would pipe the HTTPClient payload stream directly into the deserializer.
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// --- PIN DEFINITIONS ---
#define PIN_STATUS_LED 2
#define I2C_SDA 21
#define I2C_SCL 22
// --- DISPLAY CONFIG ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Mock API payload (simulating an HTTP GET response)
const char* mock_api_response = R"({
"location": {
"city": "Austin",
"region": "TX"
},
"current": {
"temp_c": 28.5,
"humidity": 62,
"condition": "Partly Cloudy",
"alerts": ["Heat Advisory", "Ozone Warning"]
},
"timestamp": 1715623400
})";
void setup() {
Serial.begin(115200);
pinMode(PIN_STATUS_LED, OUTPUT);
digitalWrite(PIN_STATUS_LED, LOW);
// Initialize I2C and OLED
Wire.begin(I2C_SDA, I2C_SCL);
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Halt execution
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Booting JSON Parser...");
display.display();
delay(1000);
parseAndGenerateJson();
}
void loop() {
// Main loop idle; parsing handled in setup for this demo
delay(10000);
}
void parseAndGenerateJson() {
// 1. Parse the incoming JSON (v7 syntax)
JsonDocument incomingDoc;
DeserializationError error = deserializeJson(incomingDoc, mock_api_response);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
display.clearDisplay();
display.setCursor(0,0);
display.print("ERR: ");
display.println(error.f_str());
display.display();
return;
}
// 2. Extract values safely
const char* city = incomingDoc["location"]["city"]; // "Austin"
float temp = incomingDoc["current"]["temp_c"]; // 28.5
int humidity = incomingDoc["current"]["humidity"]; // 62
// Extracting array elements
const char* first_alert = incomingDoc["current"]["alerts"][0]; // "Heat Advisory"
// 3. Render to OLED
display.clearDisplay();
display.setCursor(0,0);
display.printf("City: %s\n", city);
display.printf("Temp: %.1f C\n", temp);
display.printf("Hum: %d%%\n", humidity);
display.printf("Alert: %s\n", first_alert);
display.display();
// 4. Generate a new JSON telemetry payload to send back
JsonDocument outgoingDoc;
outgoingDoc["device_id"] = "ESP32_Node_01";
outgoingDoc["status"] = "nominal";
outgoingDoc["metrics"]["temp"] = temp;
outgoingDoc["metrics"]["uptime_ms"] = millis();
Serial.println(F("\n--- Outgoing Telemetry ---"));
serializeJsonPretty(outgoingDoc, Serial);
digitalWrite(PIN_STATUS_LED, HIGH); // Flash LED on success
}
Debugging: Exact Error Strings and Ranked Fixes
When deserializeJson() fails, it returns a DeserializationError enum. Do not guess what went wrong; print error.f_str() to the Serial monitor and match it against this decision tree.
The First Three Things to Check
- Validate the raw string: Copy the exact payload from your Serial monitor and paste it into JSONLint. Look for trailing commas, unescaped quotes, or single quotes instead of double quotes.
- Check stream termination: If reading from an HTTP stream, ensure the server isn't closing the connection before the payload finishes transmitting.
- Verify baud rate mismatches: If your Serial monitor is set to 9600 but the code uses 115200, the garbage characters you see aren't bad JSON; they are corrupted bytes.
Exact Error Strings and Ranked Causes
| Exact Error String | Most Likely Cause (Ranked) | The Fix |
|---|---|---|
InvalidInput |
1. Trailing comma in an array/object. 2. Unescaped control characters. 3. Single quotes used for keys. |
Sanitize the payload. If generating the JSON on a Python/Node backend, use native json.dumps() rather than string concatenation. |
NoMemory |
1. Heap fragmentation on 8-bit boards. 2. Payload exceeds available SRAM. |
On ESP32, this is rare unless parsing >100KB. If on an Uno, switch to JsonStreamingParser or filter the payload using DeserializationOption::Filter. |
EmptyInput |
1. HTTP client timed out before payload arrived. 2. Serial buffer read before data populated. |
Add a while(client.available() == 0) timeout loop before passing the stream to the deserializer. |
IncompleteInput |
1. TCP packet dropped mid-transmission. 2. Buffer size limit hit in HTTP client. |
Increase the HTTP client buffer size or implement chunked transfer decoding. |
yield(); or vTaskDelay(1); inside large iteration loops over JSON arrays.
Extending the Build: MQTT and Dynamic Payloads
The code above isolates the parsing logic, but real-world IoT nodes need to pull data from the network and push telemetry over MQTT. Here is how to extend or simplify this architecture.
How to Simplify (Resource Constrained)
If you are porting this to an ATtiny85 or an Arduino Nano and running out of flash memory:
- Drop the Adafruit GFX/SSD1306 libraries (they consume ~15KB of flash).
- Use
serializeJson(doc, Serial)instead ofserializeJsonPrettyto save string formatting overhead. - Use the ArduinoJson filtering feature to ignore nested objects you don't need, drastically reducing RAM overhead during deserialization.
How to Extend (Production IoT Node)
To turn this into a live MQTT telemetry node, swap the mock string for a live stream and integrate PubSubClient:
- Fetch via HTTP: Use
WiFiClientSecureandHTTPClient. Pass theclientobject directly intodeserializeJson(doc, client). This streams the JSON directly from the network buffer into the parser, avoiding the need to store the entire raw string in RAM first. - Publish via MQTT: Create a
JsonDocumentfor your sensor readings. UseserializeJson(doc, mqtt_buffer, sizeof(mqtt_buffer))to write the payload into a character array, then pass that array toclient.publish("home/sensors/node01", mqtt_buffer). - Handle Deep Sleep: If running on battery, wrap the parsing logic in a function, trigger
esp_deep_sleep_start()immediately after the MQTT publish completes, and use the ESP32's RTC memory to retain state across reboots.
For detailed memory profiling and advanced allocator configurations, refer to the ArduinoJson ESP32 PSRAM guide and the Espressif Memory Allocation documentation. By standardizing on ArduinoJson v7 and respecting the ESP32's heap boundaries, your embedded API integrations will remain stable across months of continuous uptime.






