JsonDocuments are designed to be throw-away objects. They are meant to be used during short periods when your program performs the serialization.

The best way to use ArduinoJson

To illustrate the idea of a throw-away object, let’s see the canonical way to use ArduinoJson:

struct SensorData {
   char name[32];
   int time;
   float value;
};

void writeSensorData(const SensorData& data, Stream& output)
{
  const size_t capacity = JSON_OBJECT_SIZE(3);
  StaticJsonDocument<capacity> doc;

  doc["name"] = data.name;
  doc["time"] = data.time;
  doc["value"] = data.value;

  serializeJson(doc, output);
}

void readSensorData(SensorData& data, Stream& input)
{
  const size_t capacity = JSON_OBJECT_SIZE(3) + 32;
  StaticJsonDocument<capacity> doc;

  deserializeJson(doc, input);

  strlcpy(data.name, doc["name"] | "N/A", sizeof(data.name));
  data.time = doc["time"];
  data.value = doc["value"];
}

As you can see, this program uses JsonDocument only in the serialization functions, and instances only live for a very short period of time. Also, notice that I used the custom data structure SensorData to persist the information in the rest of the program.

This approach has the following benefits:

  • The memory overhead due to JsonDocument is temporary.
  • The coupling with ArduinoJson is limited to two functions.
  • The JsonDocument doesn’t leak (see below).

Why is it wrong to reuse a JsonDocument?

JsonDocument contains a monotonic allocator: a fast and lightweight allocator that cannot release the memory. Because it cannot free memory, each time you remove or replace a value in a JsonDocument, the old value remains in memory. To release memory, you must either clear or destroy the entire JsonDocument.

ArduinoJson uses this kind of allocator because it provides the best performance with the smallest code. Most users don’t notice the problem, but you can run into it if you reuse the same JsonDocument without destroying or clearing it.

Remember that ArduinoJson is a serialization library. As such, it is designed to serialize and deserialize JSON documents, not to store the state of an application.

How to reuse a JsonDocument?

The best way to use a JsonDocument is not to reuse it: destroy it and create a new one. Don’t worry; it won’t affect the performance: destroying and creating a StaticJsonDocument requires only a bunch of instructions, and with DynamicJsonDocument, it only requires a call to free() and malloc().

clear()

The second best way to reuse a JsonDocument is to clear it between each use. You can explicitly clear a JsonDocument by calling JsonDocument::clear(), but this function is implicitly called by:

Also, you must understand that every reference acquired before clearing the JsonDocument is invalidated. In other words, you must not use a JsonArray, JsonObject, or JsonVariant created before calling JsonDocument::clear().

Here is an example:

// BAD EXAMPLE: don't do that!!!

// Acquire a reference to an object inside the JsonDocument
JsonObject config = doc["config"];

// Reuse the document
doc.clear();
doc["user"] = "benoit";

// Use the old reference 💀
int port = config["port"];

Because the config reference was created before clearing the document, it is now dangling, so the last line returns an incorrect result.

garbageCollect()

JsonDocument::clear() discards the content of the document. If this is a problem for you, you can call JsonDocument::garbageCollect(), which reclaims the lost memory blocks.

Internally, this function duplicates the JsonDocument to create a clean copy. As a result, this operation is slow and consumes a lot of memory.

As with clear, every previously acquired reference (JsonArray, JsonObject…) is now dangling.

See also

Global warming stripes by Professor Ed Hawkins (University of Reading)