mirror of
https://github.com/bblanchon/ArduinoJson.git
synced 2025-09-25 22:40:56 +02:00
Destroyed Bag of Tricks (markdown)
435
Bag-of-Tricks.md
435
Bag-of-Tricks.md
@@ -1,435 +0,0 @@
|
|||||||
Here are helper class that can help you add missing features.
|
|
||||||
|
|
||||||
## Look for a nested key
|
|
||||||
|
|
||||||
Suppose we've got this json:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"key1":1,"key2":{"subkey1":3,"subkey2":4}}
|
|
||||||
```
|
|
||||||
|
|
||||||
And we want to check presence of **subkey1** (or **subkey2**). Using **containsKey** method will not work, since **subkey1** and **subkey2** are nested keys inside **key2** value. This function allows to find key presence even if they are nested inside any key value:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
bool containsNestedKey(const JsonObject& obj, const char* key) {
|
|
||||||
for (const JsonPair& pair : obj) {
|
|
||||||
if (!strcmp(pair.key, key))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (containsNestedKey(pair.value.as<JsonObject>(), key))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#322](https://github.com/bblanchon/ArduinoJson/issues/322)
|
|
||||||
|
|
||||||
## Extract an array of integer
|
|
||||||
|
|
||||||
Suppose we have an input like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"leds":[56,60,46]}
|
|
||||||
```
|
|
||||||
|
|
||||||
And we want to get a `int[]` out of it:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
#define MAX_LED_COUNT 10
|
|
||||||
|
|
||||||
int leds[MAX_LED_COUNT];
|
|
||||||
int ledCount = root["leds"].asArray().copyTo(leds);
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#246](https://github.com/bblanchon/ArduinoJson/issues/246)
|
|
||||||
|
|
||||||
## Nested array in a nested array
|
|
||||||
|
|
||||||
Imagine you need to generate the following JSON:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"value1": "x",
|
|
||||||
"value2": [
|
|
||||||
[
|
|
||||||
"Yes",
|
|
||||||
"No"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Maybe"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The canonical way to do this is:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
JsonObject& root = jsonBuffer.createObject();
|
|
||||||
|
|
||||||
root["value1"] = "x";
|
|
||||||
JsonArray& value2 = root.createNestedArray("value2");
|
|
||||||
|
|
||||||
JsonArray& yesno = value2.createNestedArray();
|
|
||||||
yesno.add("Yes");
|
|
||||||
yesno.add("No");
|
|
||||||
|
|
||||||
JsonArray& maybe = value2.createNestedArray();
|
|
||||||
maybe.add("Maybe");
|
|
||||||
```
|
|
||||||
|
|
||||||
It's also possible to create the arrays and then put them in the object:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
JsonObject& root = jsonBuffer.createObject();
|
|
||||||
JsonObject& value2 = jsonBuffer.createArray();
|
|
||||||
JsonObject& yesno = jsonBuffer.createArray();
|
|
||||||
JsonObject& maybe = jsonBuffer.createArray();
|
|
||||||
|
|
||||||
root["value1"] = "x";
|
|
||||||
root["value2"] = value2;
|
|
||||||
value2.add(yesno);
|
|
||||||
yesno.add("Yes");
|
|
||||||
yesno.add("No");
|
|
||||||
value2.add(maybe);
|
|
||||||
maybe.add("Maybe");
|
|
||||||
```
|
|
||||||
|
|
||||||
But it's a little less efficient in term of CPU and memory.
|
|
||||||
|
|
||||||
Lastly, it's possible to manually encode the nested array with an undocumented feature:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
JsonObject& root = jsonBuffer.createObject();
|
|
||||||
root["value1"] = "x";
|
|
||||||
root["value2"] = RawJson("[[\"Yes\",\"No\"],[\"Maybe\"]");
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#252](https://github.com/bblanchon/ArduinoJson/issues/252)
|
|
||||||
|
|
||||||
## Merging JSON objects
|
|
||||||
|
|
||||||
Suppose we have three JSON objects like these:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
char json1[] = R"({
|
|
||||||
"fingerprint": "1234"
|
|
||||||
})";
|
|
||||||
|
|
||||||
char json2[] = R"({
|
|
||||||
"Trait1":{
|
|
||||||
"someValue": "4321"
|
|
||||||
}
|
|
||||||
})";
|
|
||||||
|
|
||||||
char json3[] = R"({
|
|
||||||
"Trait2":{
|
|
||||||
"anotherValue": "5555"
|
|
||||||
}
|
|
||||||
})";
|
|
||||||
|
|
||||||
JsonObject& jsonObjectRoot = jsonBuffer1.parseObject(json1);
|
|
||||||
JsonObject& jsonObjectOne = jsonBuffer2.parseObject(json2);
|
|
||||||
JsonObject& jsonObjectTwo = jsonBuffer3.parseObject(json3);
|
|
||||||
```
|
|
||||||
And we want a JSON that looks like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"fingerprint": "1234",
|
|
||||||
"SetOfObjects" : {
|
|
||||||
"Trait1":{
|
|
||||||
"someValue": "4321"
|
|
||||||
},
|
|
||||||
"Trait2":{
|
|
||||||
"anotherValue": "5555"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This function allows to merge the JSON objects:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void merge(JsonObject& dest, JsonObject& src) {
|
|
||||||
for (auto kvp : src) {
|
|
||||||
dest[kvp.key] = kvp.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Usage:
|
|
||||||
```c++
|
|
||||||
JsonObject& nestedObject = jsonObjectRoot.createNestedObject("SetOfObjects");
|
|
||||||
merge(nestedObject, jsonObjectOne);
|
|
||||||
merge(nestedObject, jsonObjectTwo);
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#332](https://github.com/bblanchon/ArduinoJson/issues/332)
|
|
||||||
|
|
||||||
## Buffered output
|
|
||||||
|
|
||||||
Here is a proxy that will put bytes in a buffer before actually writing them to the destination:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
template <size_t CAPACITY>
|
|
||||||
class BufferedPrint : public Print {
|
|
||||||
public:
|
|
||||||
BufferedPrint(Print& destination) : _destination(destination), _size(0) {}
|
|
||||||
|
|
||||||
~BufferedPrint() { flush(); }
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t c) {
|
|
||||||
_buffer[_size++] = c;
|
|
||||||
|
|
||||||
if (_size + 1 == CAPACITY) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void flush() {
|
|
||||||
_buffer[_size] = '\0';
|
|
||||||
_destination.print(_buffer);
|
|
||||||
_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Print& _destination;
|
|
||||||
size_t _size;
|
|
||||||
char _buffer[CAPACITY];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
To use this in your code:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
BufferedPrint<256> bufferedPrint(Serial)
|
|
||||||
root.printTo(bufferedPrint);
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#166](https://github.com/bblanchon/ArduinoJson/issues/166).
|
|
||||||
|
|
||||||
## Chunked output
|
|
||||||
|
|
||||||
Here is a proxy that allow to get only part of the output:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
class ChunkPrint : public Print {
|
|
||||||
public:
|
|
||||||
ChunkPrint(Print& destination, size_t from, size_t to)
|
|
||||||
: _destination(destination), _to_skip(from), _to_write(to - from) {}
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t c) {
|
|
||||||
if (_to_skip > 0) {
|
|
||||||
_to_skip--;
|
|
||||||
} else if (_to_write > 0) {
|
|
||||||
_to_write--;
|
|
||||||
return _destination.write(c);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Print& _destination;
|
|
||||||
size_t _to_skip;
|
|
||||||
size_t _to_write;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
To use this in your code:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
// print only range [10,20[
|
|
||||||
ChunkPrint chunkPrint(Serial,10,20);
|
|
||||||
root.printTo(chunkPrint);
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#206](https://github.com/bblanchon/ArduinoJson/issues/206).
|
|
||||||
|
|
||||||
## Render to ESP8266 WiFiClient
|
|
||||||
|
|
||||||
When used on [ESP8266 Arduino](https://github.com/esp8266/Arduino), there is currently not a direct way to render the JSON to WiFiClient, since it does not implement required Print interface.
|
|
||||||
This utility class serves as an adapter between `Print` and `WiFiClient` classes. Internal buffer is required, otherwise rendering will take several seconds (every byte will be sent as a separate packet). Increase buffer for faster send, if you have spare memory.
|
|
||||||
|
|
||||||
Create this as separate header file `WiFiClientPrint.h` or just include class in your program.
|
|
||||||
|
|
||||||
_Note: You need to call either `flush()` or `stop()` manually after printTo._
|
|
||||||
|
|
||||||
```c++
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <WiFiClient.h>
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
template<size_t BUFFER_SIZE = 32>
|
|
||||||
class WiFiClientPrint : public Print
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WiFiClientPrint(WiFiClient client)
|
|
||||||
: _client(client),
|
|
||||||
_length(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~WiFiClientPrint()
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_ESP_PORT
|
|
||||||
// Note: This is manual expansion of assertion macro
|
|
||||||
if (_length != 0) {
|
|
||||||
DEBUG_ESP_PORT.printf("\nassertion failed at " __FILE__ ":%d: " "_length == 0" "\n", __LINE__);
|
|
||||||
// Note: abort() causes stack dump and restart of the ESP
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t c) override
|
|
||||||
{
|
|
||||||
_buffer[_length++] = c;
|
|
||||||
if (_length == BUFFER_SIZE) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void flush()
|
|
||||||
{
|
|
||||||
if (_length != 0) {
|
|
||||||
_client.write((const uint8_t*)_buffer, _length);
|
|
||||||
_length = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop()
|
|
||||||
{
|
|
||||||
flush();
|
|
||||||
_client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
WiFiClient _client;
|
|
||||||
uint8_t _buffer[BUFFER_SIZE];
|
|
||||||
size_t _length;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage (typically inside `ESP8266WebServer` handler):
|
|
||||||
```c++
|
|
||||||
// #include "WiFiClientPrint.h"
|
|
||||||
|
|
||||||
// ESP8266WebServer _server;
|
|
||||||
// JsonObject json;
|
|
||||||
_server.setContentLength(json.measureLength());
|
|
||||||
_server.send(200, "application/json", "");
|
|
||||||
|
|
||||||
WiFiClientPrint<> p(_server.client());
|
|
||||||
json.printTo(p);
|
|
||||||
p.stop(); // Calls p.flush() and WifiClient.stop()
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#166](https://github.com/bblanchon/ArduinoJson/issues/166).
|
|
||||||
|
|
||||||
## Compute hash of JSON output
|
|
||||||
|
|
||||||
Here is how you can compute the CRC32 hash of the JSON output without consuming a lot of memory.
|
|
||||||
|
|
||||||
This can be very handy to compare two JSON trees.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
#include <FastCRC.h> // https://github.com/FrankBoesing/FastCRC
|
|
||||||
|
|
||||||
class HashPrint : public Print {
|
|
||||||
public:
|
|
||||||
HashPrint()
|
|
||||||
{
|
|
||||||
_hash = _hasher.crc32(NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t c)
|
|
||||||
{
|
|
||||||
_hash = _hasher.crc32_upd(&c, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t hash() const
|
|
||||||
{
|
|
||||||
return _hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
FastCRC32 _hasher;
|
|
||||||
uint32_t _hash;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
To use this in your code:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
HashPrint hashPrint;
|
|
||||||
root.printTo(hashPrint);
|
|
||||||
Serial.println(hashPrint.hash());
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#390](https://github.com/bblanchon/ArduinoJson/issues/390).
|
|
||||||
|
|
||||||
|
|
||||||
## Throw exception when JsonBuffer is too small
|
|
||||||
|
|
||||||
Here is a static decorator that will behave as the decorated `JsonBuffer`, except that it will throw an exception if the allocation fails:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
template <typename TJsonBuffer>
|
|
||||||
struct Throwing : TJsonBuffer {
|
|
||||||
virtual void *alloc(size_t bytes) {
|
|
||||||
void *ptr = TJsonBuffer::alloc(bytes);
|
|
||||||
if (ptr)
|
|
||||||
return ptr;
|
|
||||||
else
|
|
||||||
throw std::runtime_error("allocation failed");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
To use this in your code:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
Throwing<StaticJsonBuffer<200> > jsonBuffer;
|
|
||||||
// or
|
|
||||||
Throwing<DynamicJsonBuffer> jsonBuffer;
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#205](https://github.com/bblanchon/ArduinoJson/issues/205).
|
|
||||||
|
|
||||||
## Clone an object or an array
|
|
||||||
|
|
||||||
```c++
|
|
||||||
JsonVariant clone(JsonBuffer& jb, JsonVariant prototype)
|
|
||||||
{
|
|
||||||
if (prototype.is<JsonObject>()) {
|
|
||||||
const JsonObject& protoObj = prototype;
|
|
||||||
JsonObject& newObj = jb.createObject();
|
|
||||||
for (const auto& kvp : protoObj) {
|
|
||||||
newObj[clone(jb, kvp.key)] = clone(jb, kvp.value);
|
|
||||||
}
|
|
||||||
return newObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prototype.is<JsonArray>()) {
|
|
||||||
const JsonArray& protoArr = prototype;
|
|
||||||
JsonArray& newArr = jb.createArray();
|
|
||||||
for (const auto& elem : protoArr) {
|
|
||||||
newArr.add(clone(jb, elem));
|
|
||||||
}
|
|
||||||
return newArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prototype.is<char*>()) {
|
|
||||||
return jb.strdup(prototype.as<char*>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return prototype;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See issue [#533](https://github.com/bblanchon/ArduinoJson/issues/533)
|
|
Reference in New Issue
Block a user