From 6eef52cc9ce8bef321637a0d1777d5d4d6bfe612 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Fri, 7 Nov 2014 18:21:34 +0100 Subject: [PATCH] Writing documentation... --- doc/Generating JSON.md | 45 +---------- doc/Installing for Arduino.md | 0 doc/Memory model.md | 51 ++++++++++++ doc/Parsing JSON.md | 142 ++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 44 deletions(-) create mode 100644 doc/Installing for Arduino.md create mode 100644 doc/Memory model.md diff --git a/doc/Generating JSON.md b/doc/Generating JSON.md index 688f14d8..1b2ff138 100644 --- a/doc/Generating JSON.md +++ b/doc/Generating JSON.md @@ -28,52 +28,9 @@ Here is an example to generate `{"sensor":"gps","time":1351824120,"data":[48.756 ## Step 1: Reserve memory space -#### Introducing `StaticJsonBuffer` - Arduino JSON uses a preallocated memory pool to store the object tree, this is done by the `StaticJsonBuffer`. -Before using any function of the library you need to create a `StaticJsonBuffer`. Then you can use this instance to create arrays and objects, or parse a JSON string. - -`StaticJsonBuffer` has a template parameter that determines the number of bytes that it contains. For example, the following line create a `StaticJsonBuffer` containing 200 bytes on the stack: - - StaticJsonBuffer<200> jsonBuffer; - -The bigger the buffer is, the more complex the object tree can be, but also the more memory you need. - -#### How to determine the buffer size? - -So the big question you should have in mind right now is *How can I determine the size?*. - -There are basically two approaches here: - -1. either you can predict the content of the object tree, -2. or, you know how much memory is available. - -In the first case, you know some constraints on the object tree. For instance, let's say that your know in advance (and by that I mean "at compilation time") that you want to generate an object with 3 values, one of them being an array with 2 values, like the following: - - {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]} - -To determine the memory usage of this object tree, you use the two macros `JSON_ARRAY_SIZE(n)` `JSON_OBJECT_SIZE(n)`, both take the number of elements as a parameter. -For the example above, it would be: - - const int BUFFER_SIZE = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2); - StaticJsonBuffer jsonBuffer; - -In the second case, let's say you dynamically generate a JSON object tree of a random complexity so you can't put a limit base on that. But on the other hand, you don't want your program to crash because the object tree doesn't fit in memory. -The solution here is to determine how much memory is available, or in other words how much memory you can afford for the JSON generation. - -#### Why choosing fixed allocation? - -This fixed allocation approach may seem a bit strange, especially if your a desktop app developer used to dynamic allocation, but it make a lot of sense in an embedded context: - -1. the code is smaller -2. it uses less memory -3. it doesn't create memory fragmentation -4. it predictable - -Don't forget that, the memory is "freed" as soon as the `StaticJsonBuffer` is out of scope, like any other variable. It only hold the memory for a short amount of time. - -For that reason, you should never use a `StaticJsonBuffer` as a **global variable** because it would hold a lot of memory for the whole life of the program. +Before continuing please read the page [Arduino JSON memory model](Memory model.md) that explains everything you need to know about `StaticJsonBuffer`. ## Step 2: Build object tree in memory diff --git a/doc/Installing for Arduino.md b/doc/Installing for Arduino.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/Memory model.md b/doc/Memory model.md new file mode 100644 index 00000000..8b6903fd --- /dev/null +++ b/doc/Memory model.md @@ -0,0 +1,51 @@ +Arduino JSON memory model +========================= + +## Fixed memory allocation + +### Introducing `StaticJsonBuffer` + +Arduino JSON uses a preallocated memory pool to store the object tree, this is done by the `StaticJsonBuffer`. + +Before using any function of the library you need to create a `StaticJsonBuffer`. Then you can use this instance to create arrays and objects, or parse a JSON string. + +`StaticJsonBuffer` has a template parameter that determines the number of bytes that it contains. For example, the following line create a `StaticJsonBuffer` containing 200 bytes on the stack: + + StaticJsonBuffer<200> jsonBuffer; + +The bigger the buffer is, the more complex the object tree can be, but also the more memory you need. + +### How to determine the buffer size? + +So the big question you should have in mind right now is *How can I determine the size?*. + +There are basically two approaches here: + +1. either you can predict the content of the object tree, +2. or, you know how much memory is available. + +In the first case, you know some constraints on the object tree. For instance, let's say that your know in advance (and by that I mean "at compilation time") that you want to generate an object with 3 values, one of them being an array with 2 values, like the following: + + {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]} + +To determine the memory usage of this object tree, you use the two macros `JSON_ARRAY_SIZE(n)` `JSON_OBJECT_SIZE(n)`, both take the number of elements as a parameter. +For the example above, it would be: + + const int BUFFER_SIZE = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2); + StaticJsonBuffer jsonBuffer; + +In the second case, let's say you dynamically generate a JSON object tree of a random complexity so you can't put a limit base on that. But on the other hand, you don't want your program to crash because the object tree doesn't fit in memory. +The solution here is to determine how much memory is available, or in other words how much memory you can afford for the JSON generation. + +### Why choosing fixed allocation? + +This fixed allocation approach may seem a bit strange, especially if your a desktop app developer used to dynamic allocation, but it make a lot of sense in an embedded context: + +1. the code is smaller +2. it uses less memory +3. it doesn't create memory fragmentation +4. it predictable + +Don't forget that, the memory is "freed" as soon as the `StaticJsonBuffer` is out of scope, like any other variable. It only hold the memory for a short amount of time. + +For that reason, you should never use a `StaticJsonBuffer` as a **global variable** because it would hold a lot of memory for the whole life of the program. \ No newline at end of file diff --git a/doc/Parsing JSON.md b/doc/Parsing JSON.md index e69de29b..8d46f037 100644 --- a/doc/Parsing JSON.md +++ b/doc/Parsing JSON.md @@ -0,0 +1,142 @@ +Parsing JSON with Arduino JSON +============================== + +## Example + +Here an example that parse the string `{"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}`: + + char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + + // + // Step 1: Reserve memory space + // + StaticJsonBuffer<200> jsonBuffer; + + // + // Step 2: Deserialize the JSON string + // + JsonObject& root = jsonBuffer.parseObject(json); + + if (!root.success()) + { + Serial.println("parseObject() failed"); + return; + } + + // + // Step 3: Retrieve the values + // + const char* sensor = root["sensor"]; + long time = root["time"]; + double latitude = root["data"][0]; + double longitude = root["data"][1]; + +## Step 1: Reserve memory space + +Arduino JSON uses a preallocated memory pool to store the object tree, this is done by the `StaticJsonBuffer`. + +Before continuing please read the page [Arduino JSON memory model](Memory model.md) that explains everything you need to know about `StaticJsonBuffer`. + +## Step 2: Parse the JSON string + +You call the JSON parser through the instance of `StaticJsonBuffer`. +It exposes two function for parsing JSON: + +1. parseArray() that returns a reference to a `JsonArray` +2. parseObject() that returns a reference to a `JsonObject` + +Let's see an example. +Say we want to parse `{"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}`, it's an object so we call `parseObject` as follows: + + char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + + JsonObject& root = jsonBuffer.parseObject(json); + +As you can see `parseObject()` takes a `char*` as a parameter. +Be careful, it's not a `const char*`, the memory must be writable. +Indeed, the parser will modify the string in two cases: + +1. to insert string endings (character `\0`), +2. to translate escaped characters (like `\n` or `\t`). + +Another thing that you must keep in mind is that the string (`char json[]` in the example above) must stay in memory during the whole parsing process. +That is because the in memory object tree will store pointer to chunks of the string, so as to avoid any memory duplication. + +Now, to check if the parsing was successful, you can call `JsonObject::success()`: + + if (!root.success()) + { + // Parsing fail + } + +The result can be `false` for tree reasons: + +1. the JSON string is invalid, +2. the JSON string doesn't represent an object, +3. the `StaticJsonBuffer` is too small. + +We just saw how to parse an object, there is nothing more to say for arrays, the procedure is exactly the same. + +## Step 3: Retrieve the values + +Now that the object or array is in memory, you can extract the data very easily. + +In this section, we'll see how to do it with a `JsonObject`. +Once again, there is nothing more to say about arrays, `JsonArray` works exactly the same as `JsonObject`. + +#### Subscript operator + +The simplest way is to use the subscript operator of `JsonObject`: + + const char* sensor = root["sensor"]; + long time = root["time"]; + +You can chain the subscript operator if you have nested arrays or objects: + + double latitude = root["data"][0]; + double longitude = root["data"][1]; + +But alternatively, you can get a reference to the nested array: + + JsonArray& nestedArray = root["data"]; + +#### Casting values + +In the previous examples, the values were implicitly casted to the target type. +You can also do this explicitly + + const char* sensor = root["sensor"].asString(); + long time = root["time"].as(); + JsonArray& nestedArray = root["data"].asArray(); + +If the actual value doesn't match the target type, a default value will be return: + +1. `false` for boolean values +2. `0` for integer values +3. `NULL` for string values +4. `JsonArray::invalid()` for nested arrays +5. `JsonObject::invalid()` for nested object + +#### Check values + +If you want to know if some value is present, call `containsKey()`: + + if (root.contains("extra")) + { + // root["extra"] is valid + } + +If you want to check the type value has a certain type, call `is()`: + + if (root["extra"].is()) + { + // root["extra"] is an array + } + +You can also iterate through the key-value pairs of the object: + + for (JsonObject::itertor it=root.begin(); it!=root.end(); ++it) + { + Serial.println(it->key); + Serial.println(i->value.asString()); + }