Why does my device crash or reboot?
Reason 1: Null pointer dereferencing
Usually, a null pointer dereferencing happens when ArduinoJson returns a null, whereas the program expects a string.
Example with strcpy()
strcpy()
(or its safer equivalent strlcpy()
) is a function from the C standard library that copies a string from one place to another.
Unfortunately, its behavior is undefined when one of the arguments is null.
Here is an example that uses strlcpy()
with ArduinoJson:
char name[32];
deserializeJson(doc, input);
strlcpy(name, doc["name"], 32); // <-- BOOM!
This program works fine, except when the value "name"
is missing from the document because doc["name"]
returns NULL
.
The same thing happens if parsing fails.
Here is how to fix this program:
char name[32];
deserializeJson(doc, input);
strlcpy(name, doc["name"] | "N/A", 32);
This snippet uses the |
syntax, which allows us to change the default value.
It is equivalent to the following code:
char name[32];
deserializeJson(doc, input);
const char* jsonName = doc["name"];
if (jsonName)
strlcpy(name, doc["name"], 32);
else
strcpy(name, "N/A");
Example with strcmp()
strcmp()
is a function from the C standard library that compares two strings.
Here again, the behavior is undefined if one of the arguments is null.
deserializeJson(doc, input);
if (strcmp(doc["state"], "OK") == 0) {
// ...
}
This program works fine, except when the value "state"
is missing from the object, in which case doc["state"]
returns NULL
.
The same thing happens if parsing fails.
Here is how to fix this program:
deserializeJson(doc, input);
if (doc["state"] == "OK") {
// ...
}
Indeed, the JsonVariant
returned by doc["state"]
has a special implementation of the ==
operator that knows how to compare string safely.
Reason 2: Stack-overflow
A stack overflow happens when you have too many variables in the “stack” memory.
Before reading further, make sure that your target platform does have enough RAM to store the JsonDocument
and possibly the JSON input too:
- Use ArduinoJson Assistant to see how much memory you need.
- See How to reduce memory usage?
- See How to deserialize a very large document?
Once you’re sure that your device has enough RAM, you should move the JsonDocument
to the heap. Just replace your StaticJsonDocument
with a DynamicJsonDocument
.
If your JSON input is stored in the stack, you should move it to the heap too.
For instance, if you have a program like this:
char content[MAX_CONTENT_SIZE];
StaticJsonDocument<JSON_BUFFER_SIZE> doc;
receive(content);
deserializeJson(doc, content);
Serial.println(doc["name"].as<const char*>());
Then, you should transform it like so:
char* content = malloc(MAX_CONTENT_SIZE);
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
receive(content);
deserializeJson(doc, content);
Serial.println(doc["name"].as<const char*>());
free(content);
Alternatively, you could use a String
instead of manually calling malloc()
and free()
.
Using an ESP8266 and building in debug mode? Make sure to read this!
Reason 3: use of PROGMEM
string with the wrong type
ArduinoJson supports Flash (or PROGMEM
) string, but they must have the type const __FlashStringHelper*
.
A Flash string that is not properly typed will be treated as a RAM string, causing the program to crash.
Here is an example:
const char PROGMEM key[] = "the answer";
doc[key] = 42; // <- BOOOOOM!!!!
To use a Flash string with ArduinoJson, you must cast the pointer or use the F()
macro:
Solution 1: cast the pointer
You just need to pass the same pointer with a different type, so a cast is enough:
const char PROGMEM key_data[] = "the answer";
auto key = reinterpret_cast<const __FlashStringHelper*>(key_data);
doc[key] = 42;
Solution 2: use the macro F()
The first solution is not very pleasant to read, so Arduino ships with a macro that hides the cast:
doc[F("the answer")] = 42;
There is one drawback with the F()
, though: it doesn’t perform string interning.
That means that every call to the macro creates a new string in the program memory, even if the same string was already present.
So, be careful with this macro; otherwise, you’ll end up with multiple copies of the same string, and the program will be bigger than it should.
If you need to use the Flash string in several places, you better define a variable, like so:
auto key = F("the answer");
doc[key] = 42;
See also
- “The missing C++ course” and “Troubleshooting” in Mastering ArduinoJson