By design, ArduinoJson stores the complete JSON document in RAM. Therefore, it cannot deserialize a document that is larger that the capacity of your microcontroller. As a reminder, here are the RAM sizes of the most common microcontrollers:

Microcontroller RAM capacity Boards
ATmega328 2 KB
ATmega32u4 2.5 KB
ATMega4809 6 KB
ATmega2560 8 KB
AT90USB1286 8 KB
MKL26Z64VFT4 8 KB
MK20DX256VLH7 64 KB
ESP8266 96 KB
MK64FX512 192 KB
MK66FX1M0 256 KB
ESP32 520 KB
i.MX RT1066 1 MB

Some ESP32s come with a large external PSRAM that you can use with ArduinoJson.

ArduinoJson supports two techniques to deserialize very large inputs: “filtering” and “deserialization in chunks.” These techniques allow you to read a document that would otherwise not fit in the RAM of your microcontroller.

Filtering

Since ArduinoJson 6.15, deserializeJson() supports a filtering feature that reduces the memory consumption by ignoring irrelevant fields from the input document. It allows you to keep only the fields that matter in the JsonDocument.

To use this feature, you must create an ancillary JsonDocument that will serve as a filter. This document must contain the value true as a placeholder for each field you want to keep; every other field will be discarded. For arrays, only create one element in the filter document; it will serve as the filter for every element of the original document.

For example, suppose your input looks like:

{
  "list": [
    {"temperature":21.2,"humidity":68.9,"pressure":1003},
    {"temperature":19.7,"humidity":62.1,"pressure":1007},
    {"temperature":18.6,"humidity":59.8,"pressure":1009}
   ] 
}

If you only want to keep the temperature field, you must create the following filter:

{
  "list": [
    {"temperature":true}
   ] 
}

Let’s put this example into code. Here is how we can create the filter document:

StaticJsonDocument<64> filter;
filter["list"][0]["temperature"] = true;

Once the filter document is ready, you must wrap it with DeserializationOption::Filter, and pass it to deserializeJson():

deserializeJson(doc, input, DeserializationOption::Filter(filter));

After executing this line, doc will contains the following document:

{
  "list": [
    {"temperature":21.2},
    {"temperature":19.7},
    {"temperature":18.6}
   ] 
}

In this case, filtering reduced the memory consumption in half.

The OpenWeatherMap case-study in Mastering ArduinoJson shows how to use this technique in a real project.

Deserialization in chunks

Instead of parsing the whole JSON document in one shot, you can parse only a part of it and repeat the operation.

For example, suppose you are interested in the “characters” array from the following JSON document:

{
   "characters": [
        {"name":"Homer Simpson"},
        {"name":"Marge Simpson"},
        {"name":"Bart Simpson"},
        {"name":"Lisa Simpson"},
        // ...
    ]
}

If you try to deserialize the JSON document, it will probably not fit in the RAM of the microcontroller. However, you could easily deserialize each JSON object (i.e., each “character”), one by one.

One cool feature of ArduinoJson is that, when it parses an object from a Stream, it stops reading as soon as it sees the closing brace (}). The same is true with arrays: it stops reading as soon as it sees the closing bracket (]).

This feature allows you to parse streams in chunks: you just need to call deserializeJson() in a loop. Of course, you must skip the commas (,) between the objects.

Here is how this technique works:

  1. Jump to the beginning of the array.
  2. Call deserializeJson()
  3. Read the next character; if it’s a comma, go to 2.

For step 1, you can use Stream::find(); for step 2, you can use Stream::findUntil()

Here is how you can implement this technique:

input.find("\"characters\":[");
do {
    deserializeJson(doc, input);
    // ...
} while (input.findUntil(",","]"));

The Reddit case-study in Mastering ArduinoJson shows how to use this technique in a real project.