ESP32 JSON: Read from file

Introduction

In this tutorial we are going to learn how to read and parse a JSON object from a file stored on the ESP32 file system. We will be using the Arduino core to program the ESP32.

We are going to use the nlohmann/json library to parse the JSON. For an introductory tutorial on how to install it as an Arduino library and get started, please check here.

To be able to read and write files, we first need to mount a file system on the ESP32. We will be using the SPIFFS file system. You can read this previous tutorial to learn more about SPIFFS on the ESP32.

In our example below, we are going to write the JSON file first and then read it. If instead you want to upload the file beforehand, you can leverage the Arduino IDE SPIFFS file system upload plugin.

In order to read and write files from the file system, we are going to use the ifstream and ofstream C++ classes. The usage of these classes to interact with the SPIFFS file system was covered here.

The tests shown 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, operating on Windows 8.1. The version of the Nlohmann/json library used was 3.10.2

Reading a JSON object from a file

As usual, we will start our code by the library includes:

  • SPIFFS.h: Allows to mount the SPIFFS file system, from where we are going to get the file.
  • json.hpp: Exposes the JSON handling functionality.
  • fstream: Allows the usage of the classes to write and read files from the file system (ifstream and ofstream).

After this we will move on to the Arduino setup, where we will take care of the rest of the code. We will start by opening a serial connection, so we can output the result of our program.

Serial.begin(115200);

Then we will mount the SPIFFS file system. Only after the mounting process we will be able to read and write files. This is done simply by calling the begin method on the SPIFFS extern variable, which becomes available from the SPIFFS.h include.

Note that, as input of the begin method, we will pass the Boolean value true, to ensure that if the mounting procedure fails the file system is formatted. If you don’t want this behavior, you should pass false instead.

As output, the begin method returns a Boolean indicating if the mounting process was successful (true) or failed (false). We will be using this return value for error checking and end the execution of the Arduino setup in case mounting fails, since we wouldn’t be able to execute the rest of our code.

if (!SPIFFS.begin(true)) {
  Serial.println("An Error has occurred while mounting SPIFFS");
  return;
}

In case the file system mounting occurs successfully, we will create an object of class ofstream, which we will use to write our file containing a JSON object.

std::ofstream fileToWrite;

Now that we have our ofstream object, we will call the open method, passing as input the name of the file we want to create (including the whole path).

Note that, when using file system functionalities outside the Arduino SPIFFS abstractions, we need to consider the root path of the file system, which is “/spiffs”. This root path name can be changed when calling the begin method (as can be seen here, “/spiffs” is simply the default value for this optional parameter). Nonetheless, since we don’t have a particular reason to do so on this tutorial, we will stay with the default path.

For the file name, we will call it test.txt. As such, the whole path should be “/spiffs/test.txt”.

fileToWrite.open("/spiffs/test.txt");

After this, to add content to the file, we can use the << operator (usually called insertion operator). On the left side of the operator we have our stream and on the write side the content we want to write to the file. In our case, we will write a JSON object, using a raw string literal.

For illustration purposes, our JSON object will have two properties: an integer and a string.

fileToWrite << R"(
  {
    "x": 20,
    "str": "test"
  }
)";

After this we will call the close method on our ofstream object, which will close the file associated with the stream.

fileToWrite.close();

From this point onward we should now have the file in our SPIFFS file system. As such, we will start the reading part by creating an ifstream object, passing as input of the constructor the name of the file we want to open (naturally, we should use the file name from before). Note that instead of passing the file name to the constructor directly, we could just create the object and call the open method later, like we did for the ofstream.

std::ifstream fileToRead("/spiffs/test.txt");

Then we will create a json object, which will later hold the parsed JSON string.

nlohmann::json obj;

Then we will simply use the >> operator (extraction operator) to extract the data from the stream and have it parsed to the json object.

fileToRead >> obj;

Finally, we will get the values from the JSON properties and print them to the serial port. We can do this simply by using the [] operator and the key name, and then calling the get method to perform the conversion of the element to a compatible type. You can check more detailed information about the operation on this previous tutorial.

Note that, in the case of our string property, we will get it as a std::string. Since the println function doesn’t support this type, we will need to call the c_str method before printing the string to the serial port.

Serial.println(obj["x"].get<int>());
Serial.println(obj["str"].get<std::string>().c_str());

We will leave the Arduino main loop empty, as we don’t have anything to do there.

void loop() {}

The full code is shown below

#include <SPIFFS.h>
#include <json.hpp>
#include <fstream>

void setup() {

  Serial.begin(115200);

  if (!SPIFFS.begin(true)) {
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }

  std::ofstream fileToWrite;
  fileToWrite.open("/spiffs/test.txt");
  
  fileToWrite << R"(
    {
      "x": 20,
      "str": "test"
    }
  )";
  
  fileToWrite.close();

  std::ifstream fileToRead("/spiffs/test.txt");
  
  nlohmann::json obj;
  fileToRead >> obj;

  Serial.println(obj["x"].get<int>());
  Serial.println(obj["str"].get<std::string>().c_str());
}

void loop() {}

To test the code, simply compile it and upload it to your ESP32. When the upload completes, open the Arduino IDE serial monitor. You should obtain a result similar to figure 1. As expected, we have obtained the values of both properties we have originally written to the file.

Obtaining the values of the properties of a JSON stored in the ESP32 file system.
Figure 1 – Obtaining the values of the properties of a JSON stored in the ESP32 file system.

Other types of streams

We focused this tutorial on parsing JSON objects from a file, since it is a common use case. Nonetheless, the library can handle other types of streams.

If we analyze the signature of the >> operator, it actually expects an istream. As such, ifstream is just one of the classes that extends istream, meaning that we can use the same operator for other classes that extend istream.

In the code below, we will test the JSON parsing functionality for the stringstream class.

This time, we will only need to include the json.hpp and the sstream libraries (this last one giving us access to the stringstream class).

#include <json.hpp>
#include <sstream>

In the setup function, after opening a serial connection, we will create a stringstream and then write a string containing a JSON object (we will use exactly the same JSON as the previous section). Once again, we can leverage the << operator for this.

std::stringstream stream;
stream << R"(
{
  "x": 20,
  "str": "test"
}
)";

Then, we will create our json object and use the >> operator to get the data from the stream and have it parsed.

nlohmann::json obj;
stream >> obj;

Finally, we will extract the values of the parsed object and print them to the serial port. The whole code is shown below and already includes these prints.

#include <json.hpp>
#include <sstream>

void setup() {

  Serial.begin(115200);

  std::stringstream stream;
  stream << R"(
  {
    "x": 20,
    "str": "test"
  }
  )";
  
  nlohmann::json obj;
  stream >> obj;

  Serial.println(obj["x"].get<int>());
  Serial.println(obj["str"].get<std::string>().c_str());
}

void loop() {}

Upon testing the previous code, you should get exactly the same result as figure 1, since we have used the same JSON object.

Suggested ESP32 readings

Leave a Reply