ArduinoJson 6.11.0: to Infinity and beyond!
12 June 2019
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:
- If a
JsonDocument
containsNaN
orInfinity
,serializeJson()
now producesnull
. - If the input contains
NaN
orInfinity
,deserializeJson()
now returns InvalidInput.
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.