How to determine the capacity of the JsonDocument?
How can I choose the capacity if I don’t know the input ahead of time?

There are two approaches:

  1. compute the capacity of the JsonDocument size from the shape of the JSON document
  2. use whatever amount of memory is available.

Technique 1: Compute the size from the JSON document layout

In this situation, we assume that you know what kind of input you want to parse. From that, we can predict how the JSON will be stored in memory and therefore compute the required space.

For instance, let’s say that you know in advance (and by that I mean “at compilation time”) that you need to store an object with 3 values, one of them being an array with 2 values, like the following:

{"sensor":"gps","time":1351824120,"data":[48.756080,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 capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2);
StaticJsonDocument<capacity> jsonBuffer;

This process is cumbersome and error-prone, that’s why there is a tool for that: the ArduinoJson Assistant. Simply paste your JSON document in the ArduinoJson Assistant, and it will return the right capacity.

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.

Don’t forget string duplication

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

If the input is mutable (ie a char* or a 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 (ie a const char*, a String or a Stream), the parser assumes that the input is volatile.
In that case, it makes copies of the strings in the JsonDocument.
This obviously has an impact on the size of the JsonDocument
The string duplication also happens when you construct use String while constructing a JsonObject (either as a key or as value).

So 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

If your program computes the capacity at run-time, you’re trying to be too clever.

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.

Technique 2: Use whatever amount of memory is available.

In the second case, let’s say you need to store 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 is to determine a memory budget: how much memory is available, or how much memory you can afford.

For example, I use this technique in the case study “Recursive Analyzer” in Mastering ArduinoJson because the program deals with a truly unknown input.