Why does the output contain garbage?
Description of the problem
You called serializeJson()
and expected to get the following output:
{"hello":"world"}
but instead, you got:
{"hello":"�"}
or any other kind of strange output.
Why does this happen?
Garbage in the output is the sign of dangling pointers, i.e., pointers to destructed variables.
This problem occurs when:
- The
JsonDocument
is constructed with variables that are destroyed before the call toserializeJson()
(examples 1 and 2). - A
JsonArray
,JsonObject
, orJsonVariant
refers to a destructedJsonDocument
(example 3).
Example 1: destructed string value
The following program fills a JsonDocument
with a temporary String
:
// DON'T DO THAT!!! 💀
doc["address"] = address.toString().c_str();
serializeJson(doc, Serial); // <- likely to produce garbage
The problem is that the call to address.toString()
produce a temporary String
that is destructed as soon as the line is executed.
By calling String::c_str()
, the program gets a pointer to the temporary string and gives it to ArduinoJson. Since ArduinoJson sees a const char*
, it doesn’t duplicate the string and simply saves the pointer.
The problem can be avoided by removing the call to String::c_str()
:
doc["address"] = address.toString(); // <- duplicates
serializeJson(doc, Serial);
Now, ArduinoJson sees a String
and knows that it needs to make a copy of the string in the JsonDocument
.
Example 2: destructed input string
The following function deserializes a JSON document using the “zero-copy” mode, but doesn’t keep the input in memory:
void loadConfig(JsonDocument& doc){
File file = SPIFFS.open("config.json", "r");
// DON'T DO THAT!!! 💀
size_t size = file.size();
std::unique_ptr<char[]> buf (new char[size]);
file.readBytes(buf.get(), size);
deserializeJson(doc, buf.get());
file.close();
}
Indeed, when called with a char*
(or a char[]
), deserializeJson()
uses the zero-copy mode.
In this mode, the JsonDocument
stores pointers to bytes in the input.
The zero-copy mode is very efficient, but it requires that the input buffer has a longer lifetime than the JsonDocument
.
To fix this function, just change the type of input to something that is read-only.
In this particular case, it’s possible to pass the file
directly:
void loadConfig(JsonDocument& doc){
File file = SPIFFS.open("config.json", "r");
deserializeJson(doc, file);
file.close();
}
Now, ArduinoJson duplicates relevant pieces of the input in the JsonDocument
.
If unlike this example, your input is not a Stream
but a plain old char*
, you can force ArduinoJson to make a copy by casting the pointer to a const char*
:
deserializeJson(doc, (const char*)input);
Example 3: destructed JsonDocument
The following program creates a JsonObject
from a temporary JsonDocument
.
// DON'T DO THAT!!! 💀
JsonObject createObject() {
StaticJsonDocument<200> doc;
JsonObject obj = doc.to<JsonObject>();
obj["hello"] = "world";
return obj;
}
The JsonObject
returned by this function points to a destructed JsonDocument
, and therefore is likely to produce garbage or crash the program.
The best way to fix this function is to pass the JsonDocument
as an argument:
JsonObject createObject(JsonDocument& doc) {
JsonObject obj = doc.to<JsonObject>();
obj["hello"] = "world";
return obj;
}
ESP only: garbage after booting the board
If you try to run the ArduinoJson examples on an ESP8266 or an ESP32, you’ll see something like this in the Serial Monitor:
!,▒▒bf4HXa>@%▒gps
1351824120
48.756081
2.302038
As you can see, the output of the program is preceded by some random characters.
This is an entirely different issue that is not related to ArduinoJson. When an ESP boots, it prints some information to the serial port. The problem is that it uses a different baud rate, so the Serial Monitor cannot decode the data. This initial baud rate might change from one board to another, but it seems to be either 74880 or 115200.
For example, on my Adafruit HUZZAH (which contains an ESP8266), it’s 74880. If I change the baud rate in both the program and the Serial Monitor, I get the following output:
ets Jan 8 2013,rst cause:2, boot mode:(3,7)
load 0x4010f000, len 1392, room 16
tail 0
chksum 0xd0
csum 0xd0
v3d128e5c
~ld
gps
1351824120
48.756081
2.302038
On my generic ESP32 board, however, I must use 115200 baud to get the following output:
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:9720
ho 0 tail 12 room 4
load:0x40080400,len:6352
entry 0x400806b8
gps
1351824120
48.756081
2.302038
You can find some information here:
See also
- Deserialization tutorial
- The “Troubleshooting” chapter in Mastering ArduinoJson details what are the common mistakes people make when using ArduinoJson and how to solve them. It also explains how to write secure code.