EDIT: I had to roll back the changes from this version, see the article

ArduinoJson 6.6.0-beta has just been released. It introduced a radical change in the design of the library: the allocator is not monotonic anymore, it can also release memory.

How older versions worked

In ArduinoJson version 5, JsonBuffer implemented a custom allocator to use the RAM efficiently. To keep the code small and reduce the memory footprint small, I implemented the simplest form of allocator: a monotonic allocator. A monotonic allocator can allocate memory, but it cannot release it. That’s why functions like JsonObject::remove() leak memory.

This following program illustrates the problem (online demo):

DynamicJsonBuffer jb;
JsonObject& root = jb.parseObject("{\"wifi\":{\"ssid\":\"hello\"},\"host\":\"myproject\"}");
Serial.println(jb.size());
root.remove("wifi");
Serial.println(jb.size()); // same value

Because the allocator is not able to release the memory in the JsonBuffer, the value returned by size() doesn’t change.

With version 6, JsonDocument replaced JsonBuffer, but the allocator remained the same.

How the new version works

Now that microcontrollers have become more powerful, we can afford a complete allocator in JsonDocument. Starting with ArduinoJson 6.6.0-beta, the allocator is now able to release and compact the memory inside the JsonDocument. Now, functions that remove or update elements do not leak anymore.

This following program highlight the new feature (online demo):

DynamicJsonDocument doc;
deserializeJson(doc, "{\"wifi\":{\"ssid\":\"hello\"},\"host\":\"myproject\"}");
Serial.println(doc.memoryUsage());
doc.as<JsonObject>().remove("wifi");
Serial.println(doc.memoryUsage()); // much lower

With this version, the value of memoryUsage() decreases after calling JsonObject::remove(), showing that the allocator properly releases all the memory associated with the removed value.

How much does it cost?

Of course, switch from a lightweight monotonic allocator to a full-fledged allocator doesn’t come for free.

The size code of the code increased, and the memory footprint is slightly higher. The tables below show the results for the two programs above.

On an Arduino UNO:

Version Program size Before remove After remove
5.13.3 4424 69 69
6.5.0 6350 64 64
6.6.0 8642 94 38

On an ESP8266:

Version Program size Before remove After remove
5.13.3 251588 100 100
6.5.0 253212 110 110
6.6.0 254636 163 63

Before jumping to conclusions, remember that version 5.13.3 has been highly optimized over the years, so it’s normal if it’s much more efficient. Of course, I’ll do my best to improve the future revisions.

Also, remember that ArduinoJson 6 will never be as efficient as version 5. When I designed version 5, most users had Arduino UNOs, so I had to optimize the efficiency, sacrificing the ease-of-use. Nowadays, most users have ESP8266, so I’m moving the cursor toward the ease-of-use side.

Is that all?

In this new version of ArduinoJson, I removed several redundant functions:

  • JsonArray::is<T>(i)
  • JsonObject::is<T>(k)
  • JsonArray::set(i,v)
  • JsonObject::set(k,v)

And I also replaced the two following function:

  • T JsonArray::get<T>(i) became JsonVariant JsonArray::get(i)
  • T JsonObject::get<T>(k) became JsonVariant JsonObject::get(k)

Conclusion

This new allocation strategy should enable new usages of the library.

For example, it’s now possible to use a JsonDocument as a global variable. Everybody knows I don’t recommend doing that, but many users do it already.

I’d be glad to hear your comments and suggestions about ArduinoJson 6.

Stay informed!

...or subscribe to the RSS feed