ArduinoJson is quite unusual because it uses a fixed memory allocation strategy. This is cool because it allows your program to be smaller, faster, and not prone to heap fragmentation. Unfortunately, it requires a little more work from your side.

When you create a JsonDocument, you must specify the memory capacity of the document. The larger the capacity is, the more complex the document can be, but also, the more memory is consumed.

This page explains how to size your JsonDocument correctly, depending on your use case.

Techniques

Here are the four techniques that you can use to determine the capacity of your JsonDocument. The technique you choose depends on the amount of knowledge you have on your document and your platform.

If you know what the document looks like, choose technique 1 or 2.
If you cannot predict the shape of the document, choose technique 3 or 4.

Technique 1: use the ArduinoJson Assistant

The first technique consists in using a tool that does the job for you.

The ArduinoJson Assistant is an online tool that helps you determine the capacity of a JsonDocument. It analyzes a JSON document that you provide and computes the required capacity.

To use the ArduinoJson Assistant:

  1. Go to arduinojson.org/assistant
  2. Paste a sample JSON document on the left side
  3. Read the required capacity on the right side

Additionally, the ArduinoJson Assistant generates two sample programs: one shows how to serialize the JSON document, the other shows how to deserialize the JSON document.

Technique 2: compute the capacity with macros

The second technique consists in writing an expression that computes the capacity.

If you know the exact layout of the document, you can precisely compute the required size. ArduinoJson provides two macros help you do that:

  1. JSON_OBJECT_SIZE(n) returns the size of a JSON object with n members.
  2. JSON_ARRAY_SIZE(n) returns the size of a JSON array with n elements.

On top of that, you need to add the capacity required to store the strings that ArduinoJson needs to duplicate (for read-only inputs, for example).

Let’s see an example. Suppose your JSON document is:

{"values":[1,2,3]}

Then the required capacity is:

const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(3);

If ArduinoJson needs to copy the strings (because you called deserializeJson() with a read-only input, for example), then the required capacity is:

const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(3) + 7;

The seven bytes correspond to the characters of the string "values", including the terminator.

Technique 3: set a memory budget

The third technique consists in defining a budget.

If your program must accept different kinds of input, and you cannot predict what the layout would be, I recommend that you set a memory budget for the JSON serialization.

At design-time, you decide how much memory you can afford to spend on JSON serialization. This requires you to know your program and your platform very well, so you know how much memory should be available.

This technique is particularly suitable for StaticJsonDocument because the capacity is known at compile time.

For example, I use this technique in the case study “Recursive Analyzer” in Mastering ArduinoJson.

Technique 4: use whatever RAM is available

The last technique consists in allocating as much memory as possible.

This technique is similar to the previous one, except that you know the capacity at run-time, so you cannot use a StaticJsonDocument.

Because DynamicJsonDocument needs a contiguous block of memory, this technique doesn’t work well when the heap is fragmented.

The details depend on your platform. Here is how you could do on ESP8266 :

DynamicJsonDocument doc(ESP.getMaxFreeBlockSize());

And here is for ESP32 :

DynamicJsonDocument doc(ESP.getMaxAllocHeap());

In both examples, we call a function that returns the size of the largest block of free memory, which allows you to allocate the largest possible DynamicJsonDocument.

This technique consumes a lot of RAM, so only use it for short-lived DynamicJsonDocument. In other words, destruct the DynamicJsonDocument as soon as possible. Alternatively, you can call shrinkToFit() to reduce the memory usage, but beware of the heap fragmentation.

For a detailed explanation, see the section “Automatic Capacity” in the “Advanced Techniques” chapter of Mastering ArduinoJson.

What if your input varies?

If your program must deserialize a JSON document that changes, you must allocate a JsonDocument that is large enough for the largest valid input.

For example, if your “wifi” array supports up to four access-point objects, create a dummy JSON document maxed out like this:

{
    "wifi": [
        {
            "SSID": "XXXXXXXXXXXX",
            "password": "XXXXXXXXXXXX",
        },
        {
            "SSID": "XXXXXXXXXXXX",
            "password": "XXXXXXXXXXXX",
        },
        {
            "SSID": "XXXXXXXXXXXX",
            "password": "XXXXXXXXXXXX",
        },
        {
            "SSID": "XXXXXXXXXXXX",
            "password": "XXXXXXXXXXXX",
        },
    ]
}

Then, pass this document to the ArduinoJson Assistant; this way you’ll get a JsonDocument that supports all valid inputs.

For example, I use this technique in the case study “Configuration in SPIFFS” in Mastering ArduinoJson.

What if I don’t know my input ahead of time?

I really doubt that you don’t know anything about your input, but if that’s the case, use technique 3 (memory budget) or technique 4 (largest possible capacity).

Don’t forget string duplication

Depending on the type of input, the parser uses a different strategy concerning string.

If the input is mutable (char* or char[]), the parser assumes that the input buffer is persistent.
In that case, it stores pointers to the string in the input.
This is the “zero-copy” mode.

On the other hand, if the input is read-only (const char*, String, or Stream), the parser assumes that the input is volatile.
In that case, the parser copies the strings to the JsonDocument.

String duplication also happens when you construct a document.
If you insert a value (or a key) of type String, std::string, or const __FlashStringHelper, ArduinoJson stores a copy in the JsonDocument.

Don’t forget to take this into account when you compute the capacity of the JsonDocument.
The ArduinoJson Assistant shows how much extra bytes are required for the string duplications.

Don’t overthink this problem

Keep things simple.
If your program computes the capacity at run-time, you’re trying to be too smart.

You should not be looking for the exact memory consumption in each possible configuration. Instead, you should pick one size that is big enough to support all valid configurations.