ArduinoJson 6.10.0 has just been released. This revision brings two new features:

  1. proper handling of integer overflows
  2. support for custom allocators

Let’s see that in detail.

Integer overflow

When you increment the value of an integer beyond its capacity, you cause an “integer overflow.” In C++, unsigned integer overflow is well defined: when value passes the maximum, it restarts from zero. For example, suppose you have a uint8_t that contains 256, if you increment the value by 1, it goes back to 0.

This example is all right because we used an unsigned integer. Unfortunately, things are not as clear with signed integers.

In C++, signed integer overflow has undefined behavior (UB). In practice, the value wraps as we saw with the unsigned integer, but it’s not mandatory, and it’s rarely what your program expects. For example, suppose you have two positive values in two int8_t, if you add them, the result might be negative. Yes: you added two positive integers but you got a negative result; that’s why I said that it’s rarely what you expect.

When deserializing a JSON document, there are several situations where you might get an integer overflow. For example, on a machine that stores integers with 32 bits, any input that contains an integer above 2147483647 causes an overflow.

In the past, ArduinoJson simply ignored this problem; now, version 6.10 handles integer overflows at three places: in the parser, in is<T>(), and in as<T>().

To prevent integer overflows in the parser, ArduinoJson switches to floating-point storage as soon as the value cannot fit in an integer. When this happens, you need to call as<float>() instead of as<int>().

is<T>() returns false if the value cannot fit in an integer of type T. For example if the value is 512, is<char>() returns false, but is<int>() return true.

Similarly, as<T>() returns 0 if the value cannot fit in an integer of type T. For example, if the value is 512, as<char>() returns 0, but as<int>() return 512.

Custom allocators

DynamicJsonDocument allocates its memory pool using the standard C functions malloc() and free(). This behavior is sufficient for most usages, but sometimes, you need to use other memory management functions. For example, if you want to use the external SPI memory on the WROVER module, you must call heap_caps_malloc(MALLOC_CAP_SPIRAM) instead of the usual malloc().

ArduinoJson 6.10 supports custom allocators through the template class BasicJsonDocument<T>. You must pass a custom allocator class as the template parameter T. This class must implement two functions: allocate() and deallocate(). allocate() has a signature similar to malloc(): it takes one parameter that specifies the size and returns a void*. deallocate() has a signature similar to free(): it takes a void* that points to the buffer to release.

DynamicJsonDocument is implemented this way: it uses the allocator class DefaultAllocator which simply calls malloc() and free(). Here is how DynamicJsonDocument is defined.

struct DefaultAllocator {
  void* allocate(size_t n) {
    return malloc(n);
  }

  void deallocate(void* p) {
    free(p);
  }
};

typedef BasicJsonDocument<DefaultAllocator> DynamicJsonDocument;

You can copy this snippet in your project and substitute the allocation function for creating your custom JsonDocument that works with another allocator. For example, the following class SpiRamJsonDocument uses the external SPI memory on ESP32:

struct SpiRamAllocator {
  void* allocate(size_t size) {
    return heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
  }
  void deallocate(void* pointer) {
    heap_caps_free(pointer);
  }
};

using SpiRamJsonDocument = BasicJsonDocument<SpiRamAllocator>;

Conclusion

I hope you’ll love this new version of the library. You can see the complete changelog here.

As usual, please open an issue on GitHub if you need assistance. Also, remember that you can support the project by purchasing the book Mastering ArduinoJson.

See you!

Stay informed!

...or subscribe to the RSS feed