Introduction
In this tutorial we are going to learn how to use the Nlohmann/json library to apply the JSON patch operation to an object, using the ESP32 and the Arduino core. JSON patch allows to start from a source object, define a set of operations that describe how that object should be modified, and obtain a target object that is the result of those operations.
Note that JSON patch (RFC here) should not be confused with JSON Merge Patch (RFC here), which is also supported by the Nlohmann/json library. We are only going to cover JSON patch on this post.
Also take in consideration that we have covered on this previous tutorial the JSON diff operation, which basically outputs a set of operations that can be used for a patch (they respect the expected syntax, meaning they can be directly used) and represent the difference between two objects.
It is also important to mention that Nlohmann/json is a generic C++ library, meaning that the code we are using here can easily be applied in a regular C++ program, outside the scope of the ESP32 programming. In the “Suggested ESP32 Readings” section you can find a link to a tutorial that explains how to install Nlohmann/json as an Arduino library.
In this tutorial we are not going to cover all the existing patch operations. You can consult the full list here.
The tests shown in the sections below were performed on a ESP32-E FireBeetle board from DFRobot. The Arduino core version used was 2.0.0 and the Arduino IDE version was 1.8.15, working on Windows 8.1. The version of the Nlohmann/json library used was 3.10.2.
The code
We will start by including the json.hpp library, so we have access to all the JSON related functionalities, including the JSON patch.
#include <json.hpp>
Moving on to the Arduino setup, we will start by opening a serial connection, so we can later print the result of the patch operation.
Serial.begin(115200);
Then we are going to define a string containing a possible person JSON object. After parsing this string to a json object, it will be the source over which we are going to apply the patch.
The properties this object will contain are the following (note that they were arbitrarily defined for illustration purposes):
- name: name of the person.
- age: age of the person.
- confidential: a possible confidential information that will simply be a string.
- address: an object that contains two nested properties, namely street and code, and represents the address of the person.
char personStr[] = R"(
{
"name": "John",
"age": 10,
"confidential": "something",
"address": {
"street": "St. Street",
"code": "1234-12"
}
}
)";
Now that we have our base object, we are going to define some patch operations that will transform it in another object. Once again, we are going to define some arbitrary operations for illustration purposes:
- Adding a new property called “nationality“, with the value “portuguese”.
- Removing the property “confidential“.
- Updating the values of both “code” and “street“, inside “address“.
Starting by the “nationality” property we want to add to our JSON, the operation is called “add“. The “path” is simply “/nationality“, as this property should be added at the root of the object. The “value” property of the patch object should indicate what is the value of this new “nationality” property, which we mentioned to be “portuguese“. To better illustrate, the expected patch object is shown below.
{
"op": "add",
"path": "/nationality",
"value": "portuguese"
}
The removal operation has a simpler syntax, as we only need to set the “op” as “remove” and the “path” as the path of the property to be removed.
{
"op": "remove",
"path": "/confidential"
}
Finally, updating values corresponds to the “replace” operation. Since we want to update nested properties, the “path” for both objects should reflect that. Finally, as “value” we should set the final value we want to assign to the properties.
{
"op": "replace",
"path": "/address/code",
"value": "5785-214"
},
{
"op": "replace",
"path": "/address/street",
"value": "New Avenue"
}
Mapping these patch operations to a JSON array stored in a string, we have the following:
char patchStr[] = R"(
[
{
"op": "add",
"path": "/nationality",
"value": "portuguese"
},
{
"op": "remove",
"path": "/confidential"
},
{
"op": "replace",
"path": "/address/code",
"value": "5785-214"
},
{
"op": "replace",
"path": "/address/street",
"value": "New Avenue"
}
]
)";
Now that we defined both strings, we will parse each one to its own json object.
nlohmann::json person = nlohmann::json::parse(personStr);
nlohmann::json patch = nlohmann::json::parse(patchStr);
To apply the patch to the original object, we simply need to call the patch method, passing as input the json object representing the patch. This method returns as output a new json object that corresponds to the result of the patch operation. Note that this operation won’t mutate the original object.
nlohmann::json patchedPerson = person.patch(patch);
To finalize, we are going to serialize the resulting object and print it to the serial port. We do this with a call to the dump method, passing as input the indent level we want.
std::string serializedObject = patchedPerson.dump(3);
Serial.println(serializedObject.c_str());
The complete code is shown below.
#include <json.hpp>
void setup() {
Serial.begin(115200);
char personStr[] = R"(
{
"name": "John",
"age": 10,
"confidential": "something",
"address": {
"street": "St. Street",
"code": "1234-12"
}
}
)";
char patchStr[] = R"(
[
{
"op": "add",
"path": "/nationality",
"value": "portuguese"
},
{
"op": "remove",
"path": "/confidential"
},
{
"op": "replace",
"path": "/address/code",
"value": "5785-214"
},
{
"op": "replace",
"path": "/address/street",
"value": "New Avenue"
}
]
)";
nlohmann::json person = nlohmann::json::parse(personStr);
nlohmann::json patch = nlohmann::json::parse(patchStr);
nlohmann::json patchedPerson = person.patch(patch);
std::string serializedObject = patchedPerson.dump(3);
Serial.println(serializedObject.c_str());
}
void loop() {}
Testing the code
The expected result from the previous code is shown on figure 1. As can be seen, we have obtained the expected result: the “nationality” property was added, the “confidential” property was removed and both properties nested on the “address” object were updated.
Suggested ESP32 Readings
- Deserializing JSON objects from the file system
- Serialize JSON object to file
- Getting started with the Nlohmann/json json library
- Preserve order of keys on json properties insertion