Memory Model
Fixed memory allocation
Introducing StaticJsonBuffer
ArduinoJson uses a preallocated memory pool to store the object tree, and this is done by the StaticJsonBuffer
class.
Before using any function of the library, you need to create a StaticJsonBuffer
. Then you can use this instance to create arrays and objects or parse a JSON string.
StaticJsonBuffer
has a template parameter that determines its capacity. For example, the following line creates a StaticJsonBuffer
with a capacity of 200 bytes:
StaticJsonBuffer<200> jsonBuffer;
The bigger the buffer is, the more complex the object tree can be, but also, the more memory you need.
How to determine the buffer size?
So the big question you should have in mind right now is How can I determine the size?
There are basically two approaches here:
- either you can predict the content of the object tree,
- you know how much memory is available.
In the first case, you know some constraints on the object tree. For instance, let’s say that you know in advance (and by that, I mean “at compilation time”) that you want to generate an object with three values, one of them being an array with two values, like the following:
{ "sensor": "gps", "time": 1351824120, "data": [48.75608, 2.302038] }
To determine the memory usage of this object tree, you use the two macros JSON_ARRAY_SIZE(n)
and JSON_OBJECT_SIZE(n)
, both take the number of elements as an argument.
For the example above, it would be:
const int BUFFER_SIZE = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2);
StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
If you’re in this situation, ArduinoJson Assistant will be of great help.
In the second case, let’s say you dynamically generate a JSON object tree of a random complexity, so you can’t put a limit based on that. But on the other hand, you don’t want your program to crash because the object tree doesn’t fit in memory. The solution here is to determine how much memory is available, or in other words, how much memory you can afford for the JSON object tree.
Why choose fixed allocation?
This fixed allocation approach may seem a bit strange, especially if you are a desktop application developer used to dynamic allocation, but it makes a lot of sense in an embedded context:
- the code is smaller
- it uses less memory
- it doesn’t create memory fragmentation
- it is predictable
Don’t forget that the memory is “freed” as soon as the StaticJsonBuffer
is out of scope, like any other variable. It only holds the memory for a short amount of time.
Dynamic memory allocation
This library also supports dynamic memory allocation.
However, usage of this memory model makes sense only for devices with more than 10KB of RAM.
To switch to dynamic memory allocation, simply replace:
StaticJsonBuffer<N> jsonBuffer;
by
DynamicJsonBuffer jsonBuffer;
Memory is released in DynamicJsonBuffer
’s destructor, so you don’t have to do anything special.
Memory usage
Object size for 8-bit AVR
Type | Size |
---|---|
JsonArray of N element | 4 + 8 * N |
JsonObject of N element | 4 + 10 * N |
Object size on ESP8266
Type | Size |
---|---|
JsonVariant | 8 |
JsonArray of N element | 8 + 12 * N |
JsonObject of N element | 8 + 16 * N |
Object size on ESP8266 if JsonFloat
is changed to double
Type | Size |
---|---|
JsonVariant | 16 |
JsonArray of N element | 8 + 24 * N |
JsonObject of N element | 8 + 32 * N |
Use ArduinoJson Assistant to compute the size required for your project.
Where to go next?
In the ArduinoJson ebook, the chapter “Inside ArduinoJson” explains how StaticJsonBuffer
and DynamicJsonBuffer
work, and how to choose between them.
The book also contains a quick C++ course to catch up with memory management in general. It explains the differences between “stack,” “heap” and “global” memories. It also debunks wrong assumption that memory management is done by the developer.