Mastering ArduinoJson
Mastering ArduinoJson
CREATOR OF ARDUINOJSON
Mastering ArduinoJson
Efficient JSON serialization for embedded C++
Contents
Contents iv
1 Introduction 1
1.1 About this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Introduction to JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 What is JSON? . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 What is serialization? . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.3 What can you do with JSON? . . . . . . . . . . . . . . . . . . 4
1.2.4 History of JSON . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.5 Why is JSON so popular? . . . . . . . . . . . . . . . . . . . . . 6
1.2.6 The JSON syntax . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.7 Binary data in JSON . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Introduction to ArduinoJson . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.1 What ArduinoJson is . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.2 What ArduinoJson is not . . . . . . . . . . . . . . . . . . . . . 12
1.3.3 What makes ArduinoJson different? . . . . . . . . . . . . . . . 13
1.3.4 Does size really matter? . . . . . . . . . . . . . . . . . . . . . . 15
1.3.5 What are the alternatives to ArduinoJson? . . . . . . . . . . . . 16
1.3.6 How to install ArduinoJson . . . . . . . . . . . . . . . . . . . . 17
1.3.7 The examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6 Troubleshooting 163
6.1 Program crashes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
6.1.1 Undefined Behaviors . . . . . . . . . . . . . . . . . . . . . . . . 164
6.1.2 A bug in ArduinoJson? . . . . . . . . . . . . . . . . . . . . . . 164
6.1.3 Null string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.1.4 Use after free . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.1.5 Return of stack variable address . . . . . . . . . . . . . . . . . 167
6.1.6 Buffer overflow . . . . . . . . . . . . . . . . . . . . . . . . . . 169
6.1.7 Stack overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
6.1.8 How to detect these bugs? . . . . . . . . . . . . . . . . . . . . 171
6.2 Deserialization issues . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
6.2.1 A lack of information . . . . . . . . . . . . . . . . . . . . . . . 173
6.2.2 Is input valid? . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
6.2.3 Is the JsonBuffer big enough? . . . . . . . . . . . . . . . . . . 174
6.2.4 Is there enough RAM? . . . . . . . . . . . . . . . . . . . . . . 175
6.2.5 How deep is the document? . . . . . . . . . . . . . . . . . . . . 176
6.2.6 The first deserialization works? . . . . . . . . . . . . . . . . . . 177
Contents x
8 Conclusion 241
Index 242
Chapter 3
Deserialize with ArduinoJson
ý It is not the language that makes programs appear simple. It is the pro-
grammer that make the language appear simple!
– Robert C. Martin, Clean Code: A Handbook of Agile Software
Craftsmanship
Chapter 3 Deserialize with ArduinoJson 59
Now that you’re familiar with JSON and C++, we’re going learn how to use ArduinoJ-
son. This chapter explains everything there is to know about deserialization. As we’ve
seen, deserialization is the process of converting a sequence of byte into a memory rep-
resentation. In our case, it means converting a JSON document to a hierarchy of C++
structures and arrays.
In this chapter, we’ll use a JSON response from Yahoo
Query Language (YQL) as an example. YQL is a web ser-
vice that allows fetching data from the web in a SQL-like
syntax. It is very versatile and can even retrieve data out-
side of the Yahoo realm. Here are some examples of what
you can do with YQL:
• download weather forecast (we’ll do that in this chap-
ter)
• download market data
• scrape web pages via XPath or CSS selectors
• read RSS feeds
• search the web
• search on a map
For most applications, you don’t need to create an account.
For our example, we’ll use a 3-day weather forecast of the city of New York. We’ll begin
with a simple program, and add complexity one bit at a time.
Chapter 3 Deserialize with ArduinoJson 60
Let’s begin with the most simple situation: a JSON document in memory. More pre-
cisely, our JSON document resides in the stack in a writable location. This fact is going
to matter, as we will see later.
Our example is today’s weather forecast for the city of New York:
{
"date": "08 Nov 2017",
"high": "48",
"low": "39",
"text": "Rain"
}
As you see, it’s a flat JSON document, meaning that there is no nested object or
array.
It contains the following piece of information:
1. date is the date for the forecast: November 8th, 2017
2. high is the highest temperature of the day: 48°F
3. low is the highest temperature of the day: 39°F
4. text is the textual description of the weather condition: “Rain”
There is something quite unusual with this JSON document: the integer values are
actually strings. Indeed, if you look at the high and low values, you can see that they
are wrapped in quotes, making them strings instead of integers. Don’t worry, it is a
widespread problem, and ArduinoJson handles it appropriately.
Chapter 3 Deserialize with ArduinoJson 61
In the previous chapter, we saw that this code creates a duplication of the string in
the stack. We know it is a code smell in production code, but it’s a good example for
learning. This unusual construction allows getting an input string that is writable (i.e.,
not read-only), which is important for our first contact with ArduinoJson.
As we saw in the introduction, one of the unique features of ArduinoJson is its fixed
memory allocation strategy.
Here is how it works:
1. First, you create a JsonBuffer to reserve a specified amount of memory.
2. Then, you deserialize the JSON document.
3. Finally, you destroy the JsonBuffer, which releases the reserved memory.
The memory of the JsonBuffer can be either in the stack or in the heap, depending on
the derived class you choose. If you use a StaticJsonBuffer, it will be in the stack; if
you use a DynamicJsonBuffer, it will be in the heap.
A JsonBuffer is responsible for reserving and releasing the memory used by ArduinoJson.
It is an instance of the RAII idiom that we saw in the previous chapter.
When you create a JsonBuffer, you must specify its capacity in bytes.
In the case of DynamicJsonBuffer, you set the capacity via a constructor argument:
DynamicJsonBuffer jb(capacity);
As it’s a parameter of the constructor, you can use a regular variable, whose value can
be computed at run-time.
In the case of a StaticJsonBuffer, you set the capacity via a template parameter:
StaticJsonBuffer<capacity> jb;
As it’s a template parameter, you cannot use a variable. Instead, you must use a
constant, which means that the value must be computed at compile-time. As we said
in the previous chapter, the stack is managed by the compiler, so it needs to know the
size of each variable when it compiles the program.
Now comes a tricky question for every new user of ArduinoJson: what should be the
capacity of my JsonBuffer?
To answer this question, you need to know what ArduinoJson stores in the JsonBuffer.
ArduinoJson needs to store a tree of data structure that mirrors the hierarchy of objects
in the JSON document. In other words, the JsonBuffer contains objects which relate
to one another the same way they do in the JSON document.
Therefore, the capacity of the JsonBuffer highly depends on the complexity of the JSON
document. If it’s just one object with few members, like our example, a few dozens
of bytes are enough. If it’s a massive JSON document, like WeatherUnderground’s
response, up to a hundred kilobytes are needed.
ArduinoJson provides macros for computing precisely the capacity of the JSON buffer.
The macro to compute the size of an object is JSON_OBJECT_SIZE(). Here is how to
compute the capacity of our JSON document composed of only one object containing
four elements:
Chapter 3 Deserialize with ArduinoJson 63
For our example, running on an Arduino Ethernet, I’m going to use a StaticJsonObject
for the following reasons:
1. The buffer is tiny (44 bytes).
2. The RAM is very scarce on an ATmega328 (only 2KB).
3. The size of the stack is not limited on an ATmega328.
Here is our program so far:
Now that the JsonBuffer is ready, we can parse the input. To parse an object, we just
need to call JsonBuffer::parseObject():
if (obj.success()) {
// parseObject() succeeded
} else {
// parseObject() failed
Chapter 3 Deserialize with ArduinoJson 65
ArduinoJson is not very verbose when parsing fails: the only clue is this boolean. This
design was chosen to make the code small and prevent users from bloating their code
with error checking. If I were to make that decision today, the outcome would probably
different.
However, there are a limited number of reasons why parsing could fail. Here are the
three most common causes, by order of likelihood:
1. The input is not a valid JSON document.
2. The JsonBuffer is too small.
3. There is not enough free memory.
Ü More on arduinojson.org
For an exhaustive list of reasons why parsing could fail, please refer to this
question in the FAQ: “Why parsing fails?”
Chapter 3 Deserialize with ArduinoJson 66
In the previous section, we used ArduinoJson to parse a JSON document. We now have
an in-memory representation of the JSON object, and we can inspect it.
There are multiple ways to extract the values from a JsonObject; we’ll see all of them.
Here is the first and simplest syntax:
Not everyone likes implicit casts, mainly because it messes with parameter type deduc-
tion and with the auto keyword.
We saw how to extract values from an object, but we didn’t do error checking; now
let’s talk about what happens when a value is missing.
Chapter 3 Deserialize with ArduinoJson 68
When that happens, ArduinoJson returns a default value, which depends on the type:
The two last lines (JsonArray and JsonObject) happen when you extract a nested array
or object, we’ll see that in a later section.
` No exceptions
ArduinoJson never throws exceptions. Exceptions are an excellent C++ fea-
ture, but they produce large executables, which is unacceptable for embedded
programs.
Sometimes, the default value from the table above is not what you want. In this
situation, you can use the operator | to change the default value. I call it the “or”
operator because it provides a replacement when the value is missing or incompatible.
Here is an example:
This feature is handy to specify default configuration values, like in the snippet above,
but it is even more useful to prevent a null string from propagating. Here is an exam-
ple:
strlcpy(), a function that copies a source string to a destination string, crashes if the
source is null. Without the operator |, we would have to use the following code:
char hostname[32];
const char* configHostname = config["hostname"];
if (configHostname != nullptr)
strlcpy(hostname, configHostname, 32);
else
strcpy(hostname, "arduinojson.org");
This syntax is new in ArduinoJson 5.12, and it’s only available when you use the subscript
syntax ([]). We’ll see a complete example in the case studies.
Chapter 3 Deserialize with ArduinoJson 70
So far, we extracted values from an object that we know in advance. Indeed, we knew
that the JSON object had four members (date, low, high and text) and that they were
all strings. In this section, we’ll see what tools are at our disposition when dealing with
unknown objects.
The first thing we can do is look at all the keys and their associated values. In Ar-
duinoJson, a key-to-value association, or a key-value pair, is represented by the type
JsonPair.
JsonObject::iterator it;
for (it=obj.begin(); it!=obj.end(); ++it) {
it->key // is a const char* pointing to the key
it->value // is a JsonVariant
}
As we saw, ArduinoJson stores values in a JsonVariant. This class can hold any JSON
value: string, integer… A JsonVariant is returned when you call the subscript oper-
ator, like obj["text"] (this statement is not 100% accurate, but it’s conceptually a
JsonVariant that is returned).
To know the actual type of the value in a JsonVariant, you need to call the method
is<T>(), where T is the type you want to test.
For example, if we want to test that the value in our object is a string:
// Is it a string?
if (p.value.is<char*>()) {
// Yes!
// We can get the value via implicit cast:
const char* s = p.value;
// Or, via explicit method call:
auto s = p.value.as<char*>();
}
If you use this with our JSON document from Yahoo Weather, you will find that all
values are strings. Indeed, as we said earlier, there is something special about this
example: integers are wrapped in quotes, making them strings. If you remove the
quotes around the integers, you will see that the corresponding JsonVariants now contain
integers instead of strings.
Chapter 3 Deserialize with ArduinoJson 72
obj["low"].is<int>();
obj.is<int>("low");
Here too, the two statements are equivalent and produce the same exe-
cutable.
There are a limited number of types that a variant can use: boolean, integer, float,
string, array, object. However, different C++ types can store the same JSON type; for
example, a JSON integer could be a short, an int or a long in the C++ code.
The following table shows all the C++ types you can use as a parameter for
JsonVariant::is<T>() and JsonVariant::as<T>().
` More on arduinojson.org
The complete list of types that you can use as a parameter for
JsonVariant::is<T>() can be found in the API Reference.
Chapter 3 Deserialize with ArduinoJson 73
If you have an object and want to know whether a key exists in the object, you can call
containsKey().
Here is an example:
However, I don’t recommend using this function because you can avoid it most of the
time.
Here is an example where we can avoid containsKey():
The code above is not horrible, but it can be simplified and optimized if we just remove
the call to containsKey():
This code is faster and smaller because it only looks for the key “error” once (whereas
the previous code did it twice).
Chapter 3 Deserialize with ArduinoJson 74
We’ve seen how to parse a JSON object from a Yahoo Weather forecast; it’s time to
move up a notch by parsing an array of object. Indeed, the weather forecast comes in
sequence: one object for each day.
Here is our example:
[
{
"item": {
"forecast": {
"date": "09 Nov 2017",
"high": "53",
"low": "38",
"text": "Mostly Cloudy"
}
}
},
{
"item": {
"forecast": {
"date": "10 Nov 2017",
"high": "47",
"low": "26",
"text": "Breezy"
}
}
},
{
"item": {
"forecast": {
"date": "11 Nov 2017",
"high": "39",
"low": "24",
Chapter 3 Deserialize with ArduinoJson 75
` Optimized cross-product
With Yahoo Weather, it’s possible to pass an extra parameter to change
the layout of the array to match our initial expectation. This parameter is
crossProduct=optimized. However, if we use it, we lose the ability to limit
the number of days in the forecast and we take the risk of having a response
that is too big for our ATmega328. We could get along with that, as we’ll
see in the case studies, but I want to keep things simple for your first contact
with ArduinoJson.
// Parse succeeded?
if (arr.success()) {
// Yes! We can extract values.
} else {
// No!
// The input may be invalid, or the JsonBuffer may be too small.
}
As said earlier, an hard-coded input like this would never happen in production code,
but it’s a good step for your learning process.
You can see that the expression for computing the capacity of the JsonBuffer is quite
complicated:
• There is one array of three elements: JSON_ARRAY_SIZE(3)
• In this array, there are three objects of one element: 3*JSON_OBJECT_SIZE(1)
• In each object, there is one object (item) containing one element:
3*JSON_OBJECT_SIZE(1)
Chapter 3 Deserialize with ArduinoJson 77
For complicated JSON documents, the expression to compute the capacity of the
JsonBuffer becomes impossible to write by hand. Here, I did it so that you under-
stand the process; but, in practice, we use a program to do this task.
This tool is the “ArduinoJson Assistant.” You can use it online at arduinojson.org/
assistant.
You just need to paste your JSON document in the box on the left, and the Assistant
will return the expression in the box on the right. Don’t worry, the Assistant respects
your privacy: it computes the expression locally in the browser; it doesn’t send your
JSON document to a web service.
Chapter 3 Deserialize with ArduinoJson 78
The process of extracting the values from an array is very similar to the one for objects.
The only difference is that arrays are indexed by an integer, whereas objects are indexed
by a string.
To get access to the forecast data, we need to unroll the nested objects. Here is the
code to do it, step by step:
And we’re back to the JsonObject with four elements: date, low, high and text. This
subject was entirely covered in the previous section, so there is no need to repeat.
Fortunately, it’s possible to simplify the program above with just a single line:
It may not be obvious, but the two programs above use implicit casts. Indeed,
the subscript operator ([]) returns a JsonVariant which is implicitly converted to a
JsonObject&.
Again, some programmers don’t like implicit casts, that is why ArduinoJson offer an
alternative syntax with as<T>(). For example:
Chapter 3 Deserialize with ArduinoJson 79
All of this should sound very familiar because it’s similar to what we’ve seen for ob-
jects.
When we learned how to extract values from an object, we saw that, if a member is
missing, a default value is returned (for example 0 for an int). It is the same if you use
an index that is out of the range of the array.
Now is a good time to see what happens if a complete object is missing. For example:
The index 666 doesn’t exist in the array, so a special value is returned:
JsonObject::invalid(). It’s a special object that doesn’t contain anything and whose
success() method always returns false:
Our example was very straightforward because we knew that the JSON array had pre-
cisely three elements and we knew the content of these elements. In this section, we’ll
see what tools are available when the content of the array is not known.
If you know absolutely nothing about the input, which is strange, you need to determine
a memory budget allowed for parsing the input. For example, you could decide that
10KB of heap memory is the maximum you accept to spend on JSON parsing.
This constraint looks terrible at first, especially if you’re a desktop or server application
developer; but, once you think about it, it makes complete sense. Indeed, your program
is going to run in a loop, always on the same hardware, with a known amount of
memory. Having an elastic capacity would just produce a larger and slower program
with no additional value.
However, most of the time, you know a lot about your JSON document. Indeed, there is
usually a few possible variations in the input. For example, an array could have between
zero and four elements, or an object could have an optional member. In that case, use
the ArduinoJson Assistant to compute the size of each variant, and pick the biggest.
The first thing you want to know about an array is the number of elements it contains.
This is the role of JsonArray::size():
As the name may be confusing, I insist that JsonArray::size() returns the number
of elements, not the memory consumption. If you want to know how many bytes of
memory are used, call JsonBuffer::size():
Remark that JsonObject also has a size() method returning the number of key-value
pairs, but it’s rarely useful.
3.7.3 Iteration
Now that you have the size of the array, you probably want to write the following
code:
The code above works but is terribly slow. Indeed, a JsonArray is internally stored as a
linked list, so accessing an element at a random location costs O(n); in other words, it
takes n iterations to get to the nth element. Moreover, the value of JsonArray::size()
is not cached, so it needs to walk the linked list too.
That’s why it is essential to avoid arr[i] and arr.size() in a loop, like in the example
above. Instead, you should use the iteration feature of JsonArray, like this:
With this syntax, the internal linked list is walked only once, and it is as fast as it gets.
I used a JsonObject& in the loop because I knew that the array contains objects. If it’s
not your case, you can use a JsonVariant instead.
Chapter 3 Deserialize with ArduinoJson 83
JsonArray::iterator it;
for (it=arr.begin(); it!=arr.end(); ++it) {
JsonObject& elem = *it;
}
To test the type of array elements the same way we did for object values. In short, we
can either use JsonVariant::is<T>() or JsonArray::is<T>().
Here is a code sample with all syntaxes:
// Same in a loop
for (JsonVariant& elem : arr) {
// Is the current element an object?
if (elem.is<JsonObject>()) {
// We called JsonVariant::is<JsonObject>()
}
}
Chapter 3 Deserialize with ArduinoJson 84
3.8.1 Definition
At the beginning of this chapter, we saw how to parse a JSON document that is writable.
Indeed, the input variable was a char[] in the stack, and therefore, it was writable. I told
you that this fact would matter, and it’s time to explain.
ArduinoJson behaves differently with writable inputs and read-only inputs.
When the argument passed to parseObject() or parseArray() is of type char* or char[],
ArduinoJson uses a mode called “zero-copy.” It has this name because the parser never
makes any copy of the input; instead, it will use pointers pointing inside the input
buffer.
In the zero-copy mode, when a program requests the content of a string member,
ArduinoJson returns a pointer to the beginning of the string in the input buffer. To
make it possible, ArduinoJson inserts null-terminators at the end of each string; it is
the reason why this mode requires the input to be writable.
3.8.2 An example
To illustrate how the zero-copy mode works, let’s have a look at a concrete example.
Suppose we have a JSON document that is just an array containing two strings:
["hip","hop"]
And let’s says that the variable is a char[] at address 0x200 in memory:
Chapter 3 Deserialize with ArduinoJson 85
After parsing the input, when the program requests the value of the first element,
ArduinoJson returns a pointer whose address is 0x202 which is the location of the string
in the input buffer:
We naturally expect hip to be "hip" and not "hip\",\"hop\"]"; that’s why ArduinoJson
adds a null-terminator after the first p. Similarly, we expect hop to be "hop" and not
"hop\"]", so a second null-terminator is added.
'[' '"' 'h' 'i' 'p' '"' ',' '"' 'h' 'o' 'p' '"' ']' 0
0x200
parseArray()
'[' '"' 'h' 'i' 'p' 0 ',' '"' 'h' 'o' 'p' 0 ']' 0
0x202 0x208
Adding null-terminators is not the only thing the parser modifies in the input buffer. It
also replaces escaped character sequences, like \n by their corresponding ASCII charac-
ters.
I hope this explanation gives you a clear understanding of what the zero-copy mode is
and why the input is modified. It is a bit of a simplified view, but the actual code is
very similar.
Chapter 3 Deserialize with ArduinoJson 86
As we saw, in the zero-copy mode, ArduinoJson returns pointers into the input buffer.
So, for a pointer to be valid, the input buffer must be in memory at the moment the
pointer is dereferenced.
If a program dereferences the pointer after the destruction of the input buffer, it is
very likely to crash instantly, but it could also work for a while and crash later, or it
could produce nasty side effects. In the C++ jargon, this is what we call an “Undefined
Behavior”; we’ll talk about that in “Troubleshooting.”
Here is an example:
// Declare a pointer
const char *hip;
// New scope
{
// Declare the input in the scope
char input[] = "[\"hip\",\"hop\"]";
// Parse input
JsonArray& arr = jb.parseArray(input);
// Save a pointer
hip = arr[0];
}
// input is destructed now
We saw how ArduinoJson behaves with a writable input, and how the zero-copy mode
works. It’s time to see what happens when the input is read-only.
Let’s go back to out previous example except that, this time, we change its type from
char[] to const char*:
As we saw in the C++ course, this statement creates a sequence of bytes in the “globals”
area of the RAM. This memory is supposed to be read-only, that’s why we need to add
the const keyword.
Previously, we had the whole string duplicated in the stack, but it’s not the case anymore.
Instead, the stack only contains the pointer input pointing to the beginning of the string
in the “globals” area.
As we saw in the previous section, in the zero-copy mode, ArduinoJson stores pointers
pointing inside the input buffer. We saw that it has to replace some characters of
the input with null-terminators. But, with a read-only input, ArduinoJson cannot do
that anymore; to return a null-terminated string, it needs to make copies of "hip" and
"hop".
Where do you think the copies would go? In the JsonBuffer of course!
In this mode, the JsonBuffer holds a copy of each string, so we need to increase its
capacity. Let’s do the computation for our example:
1. We still need to store an object with two elements, that’s JSON_ARRAY_SIZE(2).
2. We have to make a copy of the string "hip", that’s 4 bytes including the null-
terminator.
3. And we also need to copy the string "hop", that’s 4 bytes too.
Chapter 3 Deserialize with ArduinoJson 88
In practice, you would not use the exact length of the strings; it’s safer to add a bit
of slack, in case the input changes. My advice is to add 10% to the longest possible
string, which gives a reasonable margin.
3.9.3 Practice
Apart from the capacity of the JsonBuffer, we don’t need to change anything to the
program.
Chapter 3 Deserialize with ArduinoJson 89
// A read-only input
const char* input = "[\"hip\",\"hop\"]";
I added a call to JsonBuffer::size() which returns the current memory usage. Do not
confuse the size with the capacity which is the maximum size.
If you compile this program on an ATmega328, the variable memoryUsed will contain 28,
as the ArduinoJson Assitant predicted.
const char* is not the only type you can use. It’s possible to use a String:
It’s also possible to use a Flash string, but there is one caveat. As we said in the C++
course, ArduinoJson needs a way to figure out if the input string is in RAM or Flash. To
do that, it expects a Flash string to have the type const __FlashStringHelper*. So, if
you declare a char[] PROGMEM, it will not be considered as Flash string by ArduinoJson.
You either need to cast it to const __FlashStringHelper* or use the F() macro:
The simplest is to use the F() macro:
Chapter 3 Deserialize with ArduinoJson 90
In the next section, we’ll see another kind of read-only input: streams.
Chapter 3 Deserialize with ArduinoJson 91
In the Arduino jargon, a stream is a volatile source of data, like a serial port. As opposed
to a memory buffer, which allows reading any bytes at any location, a stream only allows
to read one byte at a time and cannot go back.
This concept is materialized by the Stream abstract class. Here are examples of classes
derived from Stream:
Ü std::istream
In the C++ Standard Library, an input stream is represented by the class
std::istream.
ArduinoJson can use both Stream and std::istream.
As an example, we’ll create a program that reads a JSON file stored on an SD card.
We suppose that this file contains the three-days forecast that we used as an example
earlier.
The program will just read the file and print the content of the weather forecast for
each day.
Here is the relevant part of the code:
Chapter 3 Deserialize with ArduinoJson 92
// Open file
File file = SD.open("weather.txt");
// Print weather
Serial.println(forecast["date"].as<char*>());
Serial.println(forecast["text"].as<char*>());
Serial.println(forecast["high"].as<int>());
Serial.println(forecast["low"].as<int>());
}
Now is the time to parse the real data coming from Yahoo Weather server.
Yahoo services use a custom language named “YQL” to perform a query. Carefully
crafting the query allows to retrieve only the information we need, and therefore reduces
the work of the microcontroller.
Chapter 3 Deserialize with ArduinoJson 93
select item.forecast.date,
item.forecast.text,
item.forecast.low,
item.forecast.high
from weather.forecast(3)
where woeid=2459115
This query asks for the weather forecast of the city of New York (woeid=2459115) and
limits the results to three days (weather.forecast(3)). As we don’t need all forecast
data, we only select relevant columns: date, text, low and high.
The YQL query is passed as an HTTP query parameters, here is the (shortened) URL
we need to fetch:
http://query.yahooapis.com/v1/public/yql?q=select%20item.forecast.date...
HTTP/1.0 200 OK
Content-Type: application/json;charset=utf-8
Date: Tue, 14 Nov 2017 09:57:39 GMT
{"query":{"count":3,"created":"2017-11-14T09:57:39Z","lang":"en-US"...
{
"query": {
"count": 3,
"created": "2017-11-14T09:57:39Z",
Chapter 3 Deserialize with ArduinoJson 94
"lang": "en-US",
"results": {
"channel": [
{
"item": {
"forecast": {
"date": "14 Nov 2017",
"high": "46",
"low": "36",
"text": "Partly Cloudy"
}
}
},
{
"item": {
"forecast": {
"date": "15 Nov 2017",
"high": "47",
"low": "38",
"text": "Mostly Cloudy"
}
}
},
{
"item": {
"forecast": {
"date": "16 Nov 2017",
"high": "52",
"low": "43",
"text": "Partly Cloudy"
}
}
}
]
}
}
}
As the class EthernetClient also derives from Stream, we can pass it directly to
Chapter 3 Deserialize with ArduinoJson 95
The following program performs the HTTP request and displays the result in the con-
sole:
// Allocate JsonBuffer
const size_t capacity = JSON_ARRAY_SIZE(3)
+ 8*JSON_OBJECT_SIZE(1)
+ 4*JSON_OBJECT_SIZE(4)
+ 300;
StaticJsonBuffer<capacity> jsonBuffer;
// Parse response
JsonObject& root = jsonBuffer.parseObject(client);
Serial.println(forecast["text"].as<char*>());
Serial.println(forecast["high"].as<int>());
Serial.println(forecast["low"].as<int>());
}
A few remarks:
1. I used HTTP 1.0 instead of 1.1 to avoid Chunked transfer encoding.
2. We’re not interested in the response’s headers, so we skip them using
Stream::find(), placing the reading cursor right at the beginning of the JSON
document.
3. Stream::find() takes a char* instead of a const char*, that’s why we need to
declare endOfHeaders.
4. As usual, I used the ArduinoJson Assistant to compute the capacity of the
JsonBuffer.
You can find the complete source code of this example in the folder YahooWeather in the
zip file. We will see two other weather services in the case studies.
Continue reading...
That was a free chapter from “Mastering ArduinoJson”; the book contains seven chap-
ters like this one. Here is what readers say:
I think the missing C++course and the troubleshooting chapter are worth
the money by itself. Very useful for C programming dinosaurs like myself.
— Doug Petican
The short C++section was a great refresher. The practical use of Arduino-
Json in small embedded processors was just what I needed for my home
automation work. Certainly worth having! Thank you for both the book
and the library. — Douglas S. Basberg
For a really reasonable price, not only you’ll learn new skills, but you’ll also be one of
the few people that contribute to sustainable open-source software. Yes, giving
money for free software is a political act!
The e-book comes in three formats: PDF, epub and mobi. If you purchase the e-book,
you get access to newer versions for free. A carefully edited paperback edition is
also available.
Ready to jump in?
Go to arduinojson.org/book and use the coupon code THIRTY to get a 30% discount.