JsonDocument stores a JSON document in memory. It owns the memory referenced by JsonArray, JsonObject, and JsonVariant.

JsonDocument contains a fixed-size memory pool, with a monotonic allocator. This design allows ArduinoJson to be very efficient but requires some discipline on your side:

  1. Because the size is fixed, you need to specify the size when you create the JsonDocument
  2. Because the allocator is monotonic, it cannot release memory when you call JsonObject::remove() for example.

I strongly recommend against using a global JsonDocument because you would be troubled by the monotonic allocator, and would make inefficient use of your RAM.

On the contrary, I recommend declaring a short-lived JsonDocument that you use only in your serialization functions.

StaticJsonDocument vs DynamicJsonDocument

You can choose to store your JsonDocument in the stack or in the heap:

You must specify the capacity of a StaticJsonDocument in a template parameter, like that:

StaticJsonDocument<256> doc;

For a DynamicJsonDocument, however, you must use a constructor argument:

DynamicJsonDocument doc(2048);

JsonDocument vs JsonVariant

JsonDocument shares many features with JsonVariant; however, there is one big difference: JsonDocument has value semantics, whereas JsonVariant has reference semantics.

On the one hand, because JsonDocument owns the data, if you copy a JsonDocument, you get a complete clone.

// make a clone of the JsonDocument
DynamicJsonDocument doc2 = doc1;

On the other hand, because JsonVariant is a reference, if you copy a JsonVariant, you only clone the reference:

// make a new reference to the same variant
JsonVariant var2 = var1;

Using a JsonDocument

When you create a JsonDocument, it is initially empty. At this stage, it’s neither an object, nor an array, and JsonDocument::isNull() returns true.

When you insert the first value in the JsonDocument, it automatically changes its type to match the call. If you use the JsonDocument like an array, it becomes an array; if you use the JsonDocument as an object, it becomes an object.

Here is a JsonDocument that implicitly becomes an object:

DynamicJsonDocument doc(1024);
doc["answer"] = 42;
// the doc contains {"answer":42}

Here is a JsonDocument that implicitly becomes an array:

DynamicJsonDocument doc(1024);
// the doc contains [42]

Sometimes, however, you’ll need to explicitly convert the JsonDocument without adding a value; for example, because you want to create an empty object. In this case you can call JsonDocument::to<T>():

DynamicJsonDocument doc(1024);
JsonObject obj =<JsonObject>();
// the doc contains {}

JsonDocument::to<T>() clears the document and converts it to the specified type. Don’t confuse this function with JsonDocument::as<T>() that returns a reference only if the requested type matches the one in the document.

Computing the capacity

As we saw, a JsonDocument has a fixed capacity which you must set in your program. This opens a big question for you: “how do I determine the capacity?”.

There are four techniques to compute the capacity of a JsonDocument.

Strategy 1: use the ArduinoJson Assistant

The ArduinoJson Assistant is an only 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
  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.

Strategy 2: compute the capacity with macros

ArduinoJson provides two macros to compute precisely the size required by a JSON document:

  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:


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 corresponds to the characters of the string "values", including the terminator.

Strategy 3: set a memory budget

Another technique to compute the capacity of a JsonDocument is to 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.

Strategy 4: use whatever RAM is available

The last technique to compute the capacity of a JsonDocument consists in using all remaining memory.

This technique is similar to the previous one, except that you know the capacity at run-time, so you can only use it for DynamicJsonDocument.

Because DynamicJsonDocument needs a contiguous block of memory, this technique will not work if the heap is fragmented.

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

const size_t capacity = ESP.getFreeHeap() - 4096;

As you see, we leave a significant margin (4KB here) to leave rooms for other variables and to allow some fragmentation.

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.

Member functions

  • as<T>() casts the root to the specified type (e.g. JsonArray or JsonObject)
  • add() adds elements to the root array
  • capacity() returns the capacity of the memory pool
  • clear() empties the document and resets the memory pool
  • containsKey() tests if the root object contains the specified key
  • createNestedArray() creates a nested array attached to the root
  • createNestedObject() create a nested object attached to the root
  • getElement() is mostly for internal use 🤫
  • getMember() is mostly for internal use 🤫
  • getOrCreateMember() is mostly for internal use 🤫
  • operator[] gets or sets values in the document
  • is<T>() tests the type of the root
  • isNull() tells if the document is null or empty
  • memoryUsage() tells how many bytes are used in the memory pool
  • nesting() returns the number of nesting layers in the document
  • remove() removes an element (or member) at the specified index (or key)
  • set() is mostly for internal use 🤫
  • shrinkToFit() reduces the capacity of the memory pool to match the current usage
  • size() returns the number of elements (or members) that the root array (or object) contains
  • to<T>() clears the document and converts it to the specified type (e.g. JsonArray or JsonObject)


Here is a program that deserializes a JSON document and stores it in the stack:

StaticJsonDocument<200> doc; // <- a little more than 200 bytes in the stack

char json[] = "{\"hello\":\"world\"}";
deserializeJson(doc, json);

const char* world = doc["hello"];

Here is a program that serializes a JSON document stored in the heap:

DynamicJsonDocument doc(200); // <- 200 bytes in the heap

doc["hello"] = "world";

serializeJson(doc, Serial); // {"hello":"world"}

See also