ESP32 inja: Built-in functions

Introduction

In this tutorial we are going to learn how to use some of inja’s built-in functions in the rendering of our templates, using the ESP32 and the Arduino core. For an introductory tutorial on how to install and get started with inja, please check here.

These functions range from string caseness manipulation to array operations, offering useful functionality out of the box. We are just going to cover a few of these functions, but you can check the full list, with examples, here.

Note that we have introduced inja mostly to be used as an HTML template template engine, like we have mentioned in the previous tutorial. Nonetheless, since the engine is agnostic if we are using HTML or some other type of text, we are going to use just some simple strings on the code sections below, to keep the templates cleaner and to focus on the built-in functions.

The inja library version used was 3.3.0 and the json library version was 3.10.2. The Arduino core version used was 2.0.0 and the Arduino IDE version was 1.8.15, running on Windows 8.1.

The tests shown below were performed on a ESP32-E FireBeetle board from DFRobot.

String case transformation

In this section we are going to learn how to use inja’s built-in functions to convert a string to uppercase and lowercase.

We will start our code by including the Nlohmann/json and the inja libraries. Remember from our introductory tutorial on inja that the Nlohmann/json lib is a dependency, as its json class is used as data source to feed the inja templates.

#include <json.hpp>
#include <inja.hpp>

Moving on to the Arduino setup, we will start by opening a serial connection. This way we can later print the string resulting from the template rendering.

Serial.begin(115200);

Then we will create a json object and assign to it a string property that has both uppercase and lowercase characters, so we can see both the uppercase and lowercase conversion in action for the same string.

nlohmann::json data;
data["mixedCaseString"] = "AbcD";

After this we will define the string templates that will convert the variable to upper and lower case. I’ve decided to define two different templates, for illustration purposes, but you can use both built-in functions in the same template if you prefer.

The function that converts string variables to uppercase is called upper and to lowercase is called lower. So, both templates look like the following (not actual code, just an illustration on the templates):

"Upper Case: {{upper(mixedCaseString)}}"
"Lower Case: {{lower(mixedCaseString)}}"

For illustration purposes, instead of assigning the templates to a string and then passing that string to the render function, we will pass them directly as first argument of this function. As second we need to pass our data object. We will store the result of both render functions in two variables.

std::string upper = inja::render("Upper Case: {{upper(mixedCaseString)}}", data);
std::string lower = inja::render("Lower Case: {{lower(mixedCaseString)}}", data);

Finally we will print the result to the serial port. Recall that the println function is not able to handle variables of type std::string, which means we need to call the c_str method before.

Serial.println(upper.c_str());
Serial.println(lower.c_str());

The final code is shown below.

#include <json.hpp>
#include <inja.hpp>

void setup() {
  Serial.begin(115200);

  nlohmann::json data;
  data["mixedCaseString"] = "AbcD";

  std::string upper = inja::render("Upper Case: {{upper(mixedCaseString)}}", data);
  std::string lower = inja::render("Lower Case: {{lower(mixedCaseString)}}", data);

  Serial.println(upper.c_str());
  Serial.println(lower.c_str());
}

void loop() {}

After compiling and uploading your code, you should see a result similar to figure 1. As expected, the original string property was converted to uppercase and lowercase.

Converting string variables to lowercase and uppercase with inja.
Figure 1 – Converting string variables to lowercase and uppercase with inja.

Array operations

In this section we are going to analyze some operations that can be performed over arrays:

  • Sorting the elements.
  • Joining the elements with a defined separator character.
  • Getting the length of the list.

We will focus our attention in the Arduino Setup function, since the rest of the code will be similar to the previous section.

We will start by defining a json object containing two lists: one where the elements will be of type int and another where the elements will be strings. We will reuse this data object to render all our templates.

nlohmann::json data;
data["intList"] = {4,3,1,10,5,8,7};
data["strList"] = {"John", "Will", "Ann"};

The first template string we will create will sort the elements of the list. Naturally, the integer list will be sorted numerically and the string list will be sorted alphabetically.

To sort a list in a template we simply need to use the sort function, specifying as input the property from the data object that holds the list we want to sort.

std::string sortTemplate = R"(
{{sort(intList)}}
{{sort(strList)}}
)";

After defining our template we will render it and print the result to the serial port.

std::string sort = inja::render(sortTemplate, data);
Serial.println(sort.c_str());

In the next template we will do a join operation over all elements of each of the arrays. To do so, we simply need to call the join function inside our template. As first input it receives the name of the property that holds the array and as second it receives a string with the separator (it doesn’t need to be a single character).

We will join the integer array using only a “|” character between elements and the string array using a separator string, for illustration purposes.

After rendering the template, we will also print it to the serial port.

std::string joinTemplate = R"(
{{join(intList, "|")}}
{{join(strList, " <---> ")}}
)";
 
std::string join = inja::render(joinTemplate, data);
Serial.println(join.c_str());

Finally we will obtain the length of each array. This is done with a call to the length function inside our template.

std::string lengthTemplate = R"(
Int list size: {{length(intList)}}
Str list size: {{length(strList)}}
)";
 
std::string length = inja::render(lengthTemplate, data);
Serial.println(length.c_str());

The complete code can be found in the snippet below.

#include <json.hpp>
#include <inja.hpp>

void setup() {
  Serial.begin(115200);

  nlohmann::json data;
  data["intList"] = {4,3,1,10,5,8,7};
  data["strList"] = {"John", "Will", "Ann"};


  std::string sortTemplate = R"(
{{sort(intList)}}
{{sort(strList)}}
  )";

  std::string sort = inja::render(sortTemplate, data);
  Serial.println(sort.c_str());


  std::string joinTemplate = R"(
{{join(intList, "|")}}
{{join(strList, " <---> ")}}
  )";
 
  std::string join = inja::render(joinTemplate, data);
  Serial.println(join.c_str());



  std::string lengthTemplate = R"(
Int list size: {{length(intList)}}
Str list size: {{length(strList)}}
  )";
 
  std::string length = inja::render(lengthTemplate, data);
  Serial.println(length.c_str());
}

void loop() {}

Upon running the code we should obtain a result like the one illustrated in figure 2. The first print shows both arrays ordered. Note that the print format is the same as if we used the array directly in a template, like covered on the previous tutorial.

Then we can see that the join operation is correctly executed both by using a single character for the first array and a string for the second. Finally, we also obtained the length of the list, as expected.

Output of the program which applies some array functions.
Figure 2 – Output of the program which applies some array functions.

Chaining function invocations

The built-in template functions we have been using operate pretty much as regular functions we use when writing our code. As such, we can actually chain their invocation, meaning that, for example, we can sort an array and then pass the result to the join function. This is precisely the test we are going to demonstrate below.

This time, we will use a simpler data object, which will contain only an array of integers. Note that the original list will be unsorted, for us to confirm everything works as expected.

nlohmann::json data;
data["intList"] = {4,3,1,10,5,8,7};

Then we will define our template string, where we will first sort the array and then perform a join operation over all its elements.

std::string tmplt = R"(
{{join(sort(intList), "|")}}
)";

Finally we will render the template and print the result to the serial port.

std::string result = inja::render(tmplt, data);
Serial.println(result.c_str());

The final code is shown below.

#include <json.hpp>
#include <inja.hpp>

void setup() {
  Serial.begin(115200);

  nlohmann::json data;
  data["intList"] = {4,3,1,10,5,8,7};

  std::string tmplt = R"(
{{join(sort(intList), "|")}}
  )";
 
  std::string result = inja::render(tmplt, data);
  Serial.println(result.c_str());
}

void loop() {}

Upon testing the code, you should get a result similar to figure 3.

Output of the order function followed by the join function over the same array.
Figure 3 – Output of the order function followed by the join function over the same array.

Leave a Reply