I released ArduinoJson 6.11.0 three weeks ago, but I didn’t find the time to write a news article about it. This new revision brings four small changes that can impact your existing project. I recommend reading this article to see if you need to update your code.

NaN and Infinity

The problem

To tell you about the first change in the library, let me show you an example:

StaticJsonDocument<200> doc;
doc["value"] = NAN;
serializeJson(doc, Serial);

This code snippet produces the following JSON document:

{"value":NaN}

OK. That looks good.

Now, let’s see the same example in JavaScript:

var doc = {};
doc["value"] = NaN;
JSON.stringify(doc);

This code snippet produces the following JSON document:

{"value":null}

Oops!

Indeed, the JSON specification doesn’t support NaN, and most JSON implementations reject it. For example, all web browser consider the JSON document invalid if it contains a NaN.

Similarly, the JSON specification doesn’t support Infinity, but ArduinoJson used to support it.

So what changed?

Several users alerted me on this problem, and I decided to change the behavior of the library to match the JSON specification:

Why did ArduinoJson do that?

I always wanted ArduinoJson to be more flexible than the JSON specification; for example, it supports:

  • comments
  • single quotes
  • no quotes for keys

My goal was to move toward the JSON5 specification: “JSON for Humans.” Here is what it says:

  • Numbers may be IEEE 754 positive infinity, negative infinity, and NaN.

What sounded to be a good idea, turned out to be a mistake because it produced invalid JSON documents.

How to get the old behavior back?

ArduinoJson 6.11 supports the old and the new behaviors; you can switch between one or the other at compile time with ARDUINOJSON_ENABLE_NAN and ARDUINOJSON_ENABLE_INFINITY.

For example, to support NaN and Infinity, like ArduinoJson 6.10, you must write:

#define ARDUINOJSON_ENABLE_NAN 1
#define ARDUINOJSON_ENABLE_INFINITY 1
#include <ArduinoJson.h>

NULL and nullptr

Again, we’ll start with an example. Suppose you have the following JSON input:

{"uuid":"42206106-8ce0-11e9-a02b-04922658cbff"}

Now, imagine that you wrote the following program to parse it:

StaticJsonDocument<200> doc;
deserializeJson(doc, input);
if (doc["uuid"] != NULL) {
    // ...
}

Does this look OK to you? It sure looked good to me at first sight, but then I remember that NULL is actually 0 (you can read my article on cpp4arduino.com), so this program is really:

// ...
if (doc["uuid"] != 0) {
    // ...
}

Does this still look OK? Well, no. We don’t want to test that UUID is not zero, we want to check if it’s present in the document.

But we also have a bigger problem: when comparing a JsonVariant with an integer, as in the if statement above, ArduinoJson 6.10 first converts the JsonVariant to an integer and then compares the values. If the conversion fails, it returns 0, so we end up with if (0 != 0) which is false, whether the value is present or not.

Longtime ArduinoJson users know that the right way to test if a JsonVariant is null is to use isNull() like that:

// ...
if (!doc["uuid"].isNull()) {
    // ...
}

You can also use containsKey() to get something more readable:

// ...
if (doc.containsKey("uuid")) {
    // ...
}

Now, with version 6.11, you can also use nullptr:

// ...
if (doc["uuid"] != nullptr) {
    // ...
}

nullptr differs from NULL because it is strongly-typed, so ArduinoJson doesn’t confuse nullptr with an integer. I invite you to read my article on cpp4arduino.com to learn more.

Implicit conversion in comparison operators

The issue with NULL revealed another problem in the comparison operator. Take the following program:

if (doc["value"] == 0) {
    // ...
}

Because ArduinoJson converted the variant to an integer, the result of the expression was true when the variant was not convertible to integer. Quite a pitfall!

To avoid that, I removed the implicit conversion in the comparison operators. Now if (variant == 0) only matches if the variant contains the integer 0. If you want to go back to the old behavior, you must explicitly cast the variant: if (variant.as<int>() == 0).

The “or” operator

The “or” operator allows you to specify a default value when the one in the JsonDocument is missing or incompatible. Here is an example:

int port = doc["port"] | 80;

ArduinoJson 6.10 added integer overflow prevention in JsonVariant::as<T>() and JsonVariant::is<T>(), but I forgot to do something about the “or” operator.

For example, in the following program, printed 44 instead of 42, because 300 overflowed the uint8_t:

doc["value"] = 300;
uint8_t answer = doc["value"] | (uint8_t)42;

To fix this problem, the “or” operator now considers out-of-range values as invalid and returns the default value. With ArduinoJson 6.11, the variable answer contains 42, as expected.

Unfortunately, to make this work, I had to remove a feature of the “or” operator. In older versions, it converted floats to integers; with version 6.11, it returns the default value instead. Here is an example:

doc["value"] = 666.0;
int answer = doc["value"] | 42;

The variable answer contains 666 in ArduinoJson 6.10 and 42 in ArduinoJson 6.11.

Conclusion

You can download ArduinoJson 6.11.0 from the Arduino Library Manager or from the release page on GitHub.

I hope the small breaking changes introduced in version 6.11 won’t impact your program.

As usual, if you have any question, please open an issue on GitHub. Lastly, remember that you can support the development of the project by purchasing Mastering ArduinoJson.

Stay informed!

...or subscribe to the RSS feed