Why shouldn’t I use a global JsonBuffer?

StaticJsonBuffer and DynamicJsonBuffer are monotonic allocators: they can allocate memory, but they cannot release it. The advantages are speed and code size; they are ultra-fast and ultra small. The drawback is that you can only use them once; they are throwaways.

It’s like drawing on a piece of paper: when you want to make a new drawing, you start with a new blank sheet. I would be crazy to erase an old sheet of paper so you can reuse it, right? Similarly, when you need to use ArduinoJson again, you must create a new JsonBuffer.

A global variable, by definition, is never destroyed, so a global JsonBuffer can only be used once (or a very few times) until it’s full and unusable. That is why you should never use a global JsonBuffer; it would be like having a single sheet of paper for your whole life!

Why such a terrible limitation?

ArduinoJson is designed to do one thing and to do it well: the JSON serialization. So before trying to use a global JsonBuffer, ask yourself first: “Am I using ArduinoJson for serialization, or am I pushing it beyond its initial intent?”

In particular, you should not use JsonObject and JsonArray to store the internal state of your program as this would be terribly inefficient. Instead, write your own data structure and use ArduinoJson only in serialization functions, as shown in What’s the best way to use the library?

The “global JsonObject configuration” anti-pattern

Many projects use a JSON file to store their configuration: the file is read at startup, and the content is kept in memory during the execution of the program.

In that situation, it’s tempting to use a global JsonObject attached to a global JsonBuffer. In theory, it’s OK to use a global read-only JsonObject, because the JsonBuffer won’t grow. But in practice, it’s a huge waste of memory and processor time. The best way to implement a global configuration object is to use custom data structures as demonstrated in the example JsonConfigFile.ino.

This is a win on four levels:

  1. Faster code (the object tree is walked only once at boot time)
  2. Smaller RAM footprint (JsonObject, JsonVariant… have significant overhead)
  3. Smaller code
  4. It decouples the memory representation from the file format, which will be very handy when the file format evolves

Reducing the size of global variables is crucial as they reduce the RAM remaining for the rest. Moreover, the JSON object tree consumes memory even if the program doesn’t use it. For example, if someone adds values in the JSON settings that are not actually used by the program, the memory will be used anyway. One could even get to a situation where the program is unable to run because all the memory will be consumed by the settings.

The .data segment anti-pattern

One might think that a legitimate usage of a global buffer is to have a StaticJsonBuffer in the .data segment so that the compiler will issue an error if there is not enough memory.

While this seems like a good idea, it wastes a lot of RAM as it reserves memory that is used only a fraction of the time.