
ConfigReader C++ library
v2.0.1
Table of contents
- Overview
- Versions
- Library files
- ConfigReader class description
- Examples
- Build and connect to your project
Overview
The ConfigReader library is designed to read and write configuration files or strings in JSON format. The library provides a simple interface to serialize application configuration parameters to and from files or strings. The library includes its own built-in JSON engine (CustomJson class) declared in the main header ConfigReader.h, eliminating all external third-party dependencies (the header CustomJson.hpp is kept for compatibility and includes ConfigReader.h). The JSON_READABLE macro supports up to 120 variables. The library uses the C++17 standard, adheres to strict compilation warning standards, and does not require any third-party dependencies to be installed in the OS.
ConfigReader is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 31.01.2022 | First version. |
| 1.1.0 | 13.02.2023 | - Changed C++ version from C++11 to C++17. |
| 1.2.0 | 26.04.2023 | - CMake updated. |
| 1.3.0 | 27.06.2023 | - Useless methods excluded. - Added examples. - Added documentation. - Repository made public. |
| 1.3.1 | 19.03.2024 | - Documentation updated. |
| 1.4.0 | 14.05.2024 | - Nlohmann JSON implementation moved to src file. - Documentation updated. |
| 1.4.1 | 05.07.2024 | - CMake changed. |
| 2.0.0 | 21.09.2026 | - Replaced external nlohmann_json dependency with built-in standalone CustomJson.hpp. - Eliminated all external dependencies for software certification. - Removed 64-variable limitation in JSON_READABLE macro. - Strict compilation warnings enabled (-Wall -Wextra -Wpedantic -Werror / /W4 /WX). - Fixed buffer overruns and typos in examples. |
| 2.0.1 | 06.10.2026 | - JSON parser: nesting depth limited to 128 levels (deep input overflowed the stack), unterminated comments rejected, surrogate pairs checked (lone surrogates become U+FFFD), numbers parsed and written independent of the C locale, number overflow reported as parse error. - Values that do not fit in the requested type are not converted (integer wrap around, undefined float to integer and double to float conversions): get(…) returns FALSE. - NaN and infinity are written as null (the output stays valid JSON). - No console output on errors. writeToFile(…) reports write errors and does not truncate the file if the text can not be created. - Move assignment of JSON values reads only the active value (undefined behaviour). - Tests extended. |
Library files
The library is supplied as source code only. The user is provided with a set of files in the form of a CMake project (repository). The repository structure is shown below:
CMakeLists.txt ---------------- Main CMake file of the library.
src --------------------------- Folder with library source code.
CMakeLists.txt ------------ CMake file of the library.
ConfigReader.h ------------ Main library header file.
ConfigReaderVersion.h ----- Header file with library version.
ConfigReaderVersion.h.in -- File for CMake to generate version header.
ConfigReader.cpp ---------- C++ implementation file.
CustomJson.hpp ------------ Compatibility header, includes ConfigReader.h.
examples ---------------------- Folder with examples.
CMakeLists.txt ------------ CMake file to include examples.
WriteAndReadFile ---------- Folder with file read/write example.
CMakeLists.txt -------- CMake file of example application.
main.cpp -------------- Source C++ file of example application.
WriteAndReadString -------- Folder with string read/write example.
CMakeLists.txt -------- CMake file of example application.
main.cpp -------------- Source C++ file of example application.
tests ------------------------- Automated compatibility test suite.
CMakeLists.txt ------------ CMake file for test suite.
main.cpp ------------------ Source C++ file for test suite.
WindowsHeaderTest.cpp ----- Test of compatibility with windows.h.
ConfigReader class description
ConfigReader class declaration
The ConfigReader.h file contains the ConfigReader class declaration:
namespace rapidpixel
{
class ConfigReader
{
public:
/// Get string of current class version.
static std::string getVersion();
/// Class constructor.
ConfigReader();
/// Class destructor.
~ConfigReader();
/// Read configuration from file.
bool readFromFile(std::string fileName);
/// Write configuration to file.
bool writeToFile(std::string fileName);
/// Read configuration from string.
bool readFromString(std::string json);
/// Write configuration to string.
bool writeToString(std::string& json);
/// Get object.
template<class T>
bool get(T& obj, std::string objName = "");
/// Set object.
template<class T>
bool set(T& obj, std::string objName = "");
/// Set const object.
template<class T>
bool set(const T& obj, std::string objName = "");
};
}
Basic principles
Before using ConfigReader, the user must define a parameters structure or class. The parameter class fields are registered with the JSON_READABLE macro for serialization to and from JSON. Parameters are serialized according to the user-defined class. Example:
/// Custom parameter class.
class CustomParam
{
public:
/// Parameter 1.
int param1{1};
/// Parameter 2.
int param2{2};
/// Declare params as readable for JSON.
JSON_READABLE(CustomParam, param1, param2)
};
/// Parameters class.
class Params
{
public:
/// Parameter.
int param1{-10};
/// Parameter.
float param2{10.0f};
/// Parameter.
double param3{-10.0};
/// Parameter.
bool param4{false};
/// Array of parameters.
float mass1[3]{0.0f, 0.0f, 0.0f};
/// Array of parameters.
int mass2[3]{0, 0, 0};
/// List of parameters.
std::vector<int> list1{};
/// List of objects.
std::vector<CustomParam> list2{};
/// Declare params as readable for JSON.
JSON_READABLE(Params, param1, param2, param3, mass1, list1, list2)
};
ConfigReader supports all fundamental types (int, int64_t, uint64_t, float, double, string, bool), standard containers (std::vector, std::map with std::string keys), C-style arrays (T[N]), and nested custom objects (std::array is not supported). Variables to be read or written must be listed in the JSON_READABLE macro. Parameters not listed in the macro (such as param4 and mass2 in the example above) are excluded from JSON serialization.
Example of JSON output produced for the class above (the fields are written in the order of the JSON_READABLE macro):
{
"Params": {
"param1": 41,
"param2": 107.0,
"param3": 214.0,
"mass1": [
236.0,
190.0,
212.0
],
"list1": [
235,
44,
169,
3
],
"list2": [
{
"param1": 33,
"param2": 187
},
{
"param1": 239,
"param2": 95
},
{
"param1": 95,
"param2": 76
},
{
"param1": 252,
"param2": 16
}
]
}
}
ConfigReader reads and writes parameters strictly according to the defined parameters class. If a required property is missing or has an incompatible type or value, the get(…) method returns false and does not change the object.
getVersion method
The getVersion() method returns a string with the ConfigReader library version. Method declaration:
static std::string getVersion();
The method can be called without a ConfigReader class instance. Example:
std::cout << "ConfigReader class version: " << ConfigReader::getVersion() << std::endl;
Console output:
ConfigReader class version: 2.0.1
readFromFile method
The readFromFile(…) method reads a configuration file, parses the JSON content, and stores it in the internal configuration tree. Method declaration:
bool readFromFile(std::string fileName);
| Parameter | Description |
|---|---|
| fileName | JSON file name. |
Returns: true if the configuration file was successfully read and parsed, false otherwise.
Example:
ConfigReader inConfig;
if (!inConfig.readFromFile("TestConfig.json"))
{
std::cout << "Can't read file" << std::endl;
}
writeToFile method
The writeToFile(…) method writes the configuration tree to a JSON file. If the file already exists, it is overwritten. The user calls set(…) before writeToFile(…) to store the parameters structure into the ConfigReader instance. Method declaration:
bool writeToFile(std::string fileName);
| Parameter | Description |
|---|---|
| fileName | File name to store parameters structure. |
Returns: true if the file was written successfully, false otherwise.
Example:
ConfigReader outConfig;
if (!outConfig.set(outParams, "Params"))
{
std::cout << "Can't copy params" << std::endl;
}
if (!outConfig.writeToFile("TestConfig.json"))
{
std::cout << "Can't write file" << std::endl;
}
readFromString method
The readFromString(…) method reads configuration parameters from a JSON string and stores them in the internal configuration tree. Method declaration:
bool readFromString(std::string json);
| Parameter | Description |
|---|---|
| json | String containing JSON structure. |
Returns: true if the string was read and parsed successfully, false otherwise.
Example:
ConfigReader inConfig;
if (!inConfig.readFromString(jsonData))
{
std::cout << "Can't read string" << std::endl;
}
writeToString method
The writeToString(…) method dumps the configuration tree to a JSON string. The user calls set(…) before writeToString(…) to store the parameters structure into the ConfigReader instance. Method declaration:
bool writeToString(std::string& json);
| Parameter | Description |
|---|---|
| json | String reference to receive the dumped JSON. |
Returns: true if the configuration was written successfully, false otherwise.
Example:
ConfigReader outConfig;
if (!outConfig.set(outParams, "Params"))
{
std::cout << "Can't copy params" << std::endl;
}
std::string jsonData;
if (!outConfig.writeToString(jsonData))
{
std::cout << "Can't write string" << std::endl;
}
get method
The get(…) method deserializes a parameter structure from the internal JSON tree that was previously loaded from a file or string. Method declaration:
template<class T>
bool get(T& obj, std::string objName = "");
| Parameter | Description |
|---|---|
| obj | Parameters object to populate (see Basic principles). |
| objName | Dot-separated path of object/section. Empty string reads root object. |
Returns: true if parameters were parsed successfully, false otherwise.
Example:
ConfigReader inConfig;
if (!inConfig.readFromFile("TestConfig.json"))
{
std::cout << "ERROR: Can't read file" << std::endl;
}
Params inParams;
if (!inConfig.get(inParams, "Params"))
{
std::cout << "ERROR: Can't read params" << std::endl;
}
set method
The set(…) method stores a parameter structure into the ConfigReader instance for subsequent writing to a file (writeToFile(…)) or string (writeToString(…)). Method declarations:
template<class T>
bool set(T& obj, std::string objName = "");
template<class T>
bool set(const T& obj, std::string objName = "");
| Parameter | Description |
|---|---|
| obj | Parameters object to store (see Basic principles). |
| objName | Dot-separated path to store the object under. |
Returns: true if the parameters were stored successfully, false otherwise.
Example:
ConfigReader outConfig;
if (!outConfig.set(outParams, "Params"))
{
std::cout << "Can't copy params" << std::endl;
}
if (!outConfig.writeToFile("TestConfig.json"))
{
std::cout << "Can't write file" << std::endl;
}
Examples
Read and write config file
The example shows how to declare a parameters class, write parameters to a file, read them back, and verify values:
#include <iostream>
#include <vector>
#include "ConfigReader.h"
/// Link namespaces.
using namespace std;
using namespace rapidpixel;
/// Custom parameter class.
class CustomParam
{
public:
/// Parameter 1.
int param1{1};
/// Parameter 2.
int param2{2};
/// Declare params as readable for JSON.
JSON_READABLE(CustomParam, param1, param2)
};
/// Parameters class.
class Params
{
public:
/// Parameter.
int param1{-10};
/// Parameter.
float param2{10.0f};
/// Parameter.
double param3{-10.0};
/// Parameter.
bool param4{false};
/// Array of parameters.
float mass1[3]{0.0f, 0.0f, 0.0f};
/// Array of parameters.
int mass2[3]{0, 0, 0};
/// List of parameters.
vector<int> list1{};
/// List of objects.
vector<CustomParam> list2{};
/// Declare params as readable for JSON.
JSON_READABLE(Params, param1, param2, param3, mass1, list1, list2)
};
int main()
{
cout << "==================================================" << endl;
cout << "ConfigReader v" << ConfigReader::getVersion() << endl;
cout << "Write and read file example" << endl;
cout << "==================================================" << endl;
cout << endl;
// Prepare parameters.
Params outParams;
outParams.param1 = rand() % 255;
outParams.param2 = static_cast<float>(rand() % 255);
outParams.param3 = static_cast<double>(rand() % 255);
outParams.param4 = true; // Will not be saved.
for (int i = 0; i < 4; ++i)
{
outParams.list1.push_back(rand() % 255);
}
for (int i = 0; i < 4; ++i)
{
CustomParam param;
param.param1 = rand() % 255;
param.param2 = rand() % 255;
outParams.list2.push_back(param);
}
for (int i = 0; i < 3; ++i)
{
outParams.mass1[i] = static_cast<float>(rand() % 255);
}
for (int i = 0; i < 3; ++i)
{
outParams.mass2[i] = static_cast<int>(rand() % 255);
}
// Copy params to config reader.
ConfigReader outConfig;
if (!outConfig.set(outParams, "Params"))
{
cout << "ERROR: Can't copy params" << endl;
return -1;
}
// Create and write JSON file.
if (!outConfig.writeToFile("TestConfig.json"))
{
cout << "ERROR: Can't write file" << endl;
return -1;
}
// Read params from file.
ConfigReader inConfig;
if (!inConfig.readFromFile("TestConfig.json"))
{
cout << "ERROR: Can't read file" << endl;
return -1;
}
// Read values from "Params" section.
Params inParams;
if (!inConfig.get(inParams, "Params"))
{
cout << "ERROR: Can't read params" << endl;
return -1;
}
// Compare params.
if (outParams.param1 != inParams.param1)
{
cout << "ERROR: param1 has wrong value" << endl;
}
if (outParams.param2 != inParams.param2)
{
cout << "ERROR: param2 has wrong value" << endl;
}
if (outParams.param3 != inParams.param3)
{
cout << "ERROR: param3 has wrong value" << endl;
}
if (outParams.param4 != inParams.param4)
{
cout << "OK: param4 not included to list for reading/writing" << endl;
}
for (int i = 0; i < 3; ++i)
{
if (outParams.mass1[i] != inParams.mass1[i])
{
cout << "ERROR: mass1[" << i << "] has wrong value" << endl;
}
}
for (int i = 0; i < 3; ++i)
{
if (outParams.mass2[i] != inParams.mass2[i])
{
cout << "OK: mass2[" << i << "] not included to list for reading/writing" << endl;
}
}
if (outParams.list1.size() == inParams.list1.size())
{
for (size_t i = 0; i < inParams.list1.size(); ++i)
{
if (outParams.list1[i] != inParams.list1[i])
{
cout << "ERROR: list1[" << i << "] has wrong value" << endl;
}
}
}
else
{
cout << "ERROR: list1 has wrong size" << endl;
}
if (outParams.list2.size() == inParams.list2.size())
{
for (size_t i = 0; i < inParams.list2.size(); ++i)
{
if (outParams.list2[i].param1 != inParams.list2[i].param1 ||
outParams.list2[i].param2 != inParams.list2[i].param2)
{
cout << "ERROR: list2[" << i << "] has wrong value" << endl;
}
}
}
else
{
cout << "ERROR: list2 has wrong size" << endl;
}
return 0;
}
Read and write parameters to string
The example shows how to write parameters to a JSON string and read them back into an object:
#include <iostream>
#include <string>
#include <vector>
#include "ConfigReader.h"
/// Link namespaces.
using namespace std;
using namespace rapidpixel;
/// Custom parameter class.
class CustomParam
{
public:
/// Parameter 1.
int param1{1};
/// Parameter 2.
int param2{2};
/// Declare params as readable for JSON.
JSON_READABLE(CustomParam, param1, param2)
};
/// Parameters class.
class Params
{
public:
/// Parameter.
int param1{-10};
/// Parameter.
float param2{10.0f};
/// Parameter.
double param3{-10.0};
/// Parameter.
bool param4{false};
/// Array of parameters.
float mass1[3]{0.0f, 0.0f, 0.0f};
/// Array of parameters.
int mass2[3]{0, 0, 0};
/// List of parameters.
vector<int> list1{};
/// List of objects.
vector<CustomParam> list2{};
/// Declare params as readable for JSON.
JSON_READABLE(Params, param1, param2, param3, mass1, list1, list2)
};
int main()
{
cout << "==================================================" << endl;
cout << "ConfigReader v" << ConfigReader::getVersion() << endl;
cout << "Write and read string example" << endl;
cout << "==================================================" << endl;
cout << endl;
// Prepare parameters.
Params outParams;
outParams.param1 = rand() % 255;
outParams.param2 = static_cast<float>(rand() % 255);
outParams.param3 = static_cast<double>(rand() % 255);
outParams.param4 = true; // Will not be saved.
for (int i = 0; i < 4; ++i)
{
outParams.list1.push_back(rand() % 255);
}
for (int i = 0; i < 4; ++i)
{
CustomParam param;
param.param1 = rand() % 255;
param.param2 = rand() % 255;
outParams.list2.push_back(param);
}
for (int i = 0; i < 3; ++i)
{
outParams.mass1[i] = static_cast<float>(rand() % 255);
}
for (int i = 0; i < 3; ++i)
{
outParams.mass2[i] = static_cast<int>(rand() % 255);
}
// Copy params to config reader.
ConfigReader outConfig;
if (!outConfig.set(outParams, "Params"))
{
cout << "ERROR: Can't copy params" << endl;
return -1;
}
// Write params to string.
string jsonData;
if (!outConfig.writeToString(jsonData))
{
cout << "ERROR: Can't write string" << endl;
return -1;
}
// Read params from string.
ConfigReader inConfig;
if (!inConfig.readFromString(jsonData))
{
cout << "ERROR: Can't read string" << endl;
return -1;
}
// Read values from "Params" section.
Params inParams;
if (!inConfig.get(inParams, "Params"))
{
cout << "ERROR: Can't read params" << endl;
return -1;
}
// Compare params.
if (outParams.param1 != inParams.param1)
{
cout << "ERROR: param1 has wrong value" << endl;
}
if (outParams.param2 != inParams.param2)
{
cout << "ERROR: param2 has wrong value" << endl;
}
if (outParams.param3 != inParams.param3)
{
cout << "ERROR: param3 has wrong value" << endl;
}
if (outParams.param4 != inParams.param4)
{
cout << "OK: param4 not included to list for reading/writing" << endl;
}
for (int i = 0; i < 3; ++i)
{
if (outParams.mass1[i] != inParams.mass1[i])
{
cout << "ERROR: mass1[" << i << "] has wrong value" << endl;
}
}
for (int i = 0; i < 3; ++i)
{
if (outParams.mass2[i] != inParams.mass2[i])
{
cout << "OK: mass2[" << i << "] not included to list for reading/writing" << endl;
}
}
if (outParams.list1.size() == inParams.list1.size())
{
for (size_t i = 0; i < inParams.list1.size(); ++i)
{
if (outParams.list1[i] != inParams.list1[i])
{
cout << "ERROR: list1[" << i << "] has wrong value" << endl;
}
}
}
else
{
cout << "ERROR: list1 has wrong size" << endl;
}
if (outParams.list2.size() == inParams.list2.size())
{
for (size_t i = 0; i < inParams.list2.size(); ++i)
{
if (outParams.list2[i].param1 != inParams.list2[i].param1 ||
outParams.list2[i].param2 != inParams.list2[i].param2)
{
cout << "ERROR: list2[" << i << "] has wrong value" << endl;
}
}
}
else
{
cout << "ERROR: list2 has wrong size" << endl;
}
return 0;
}
Build and connect to your project
Typical commands to build ConfigReader library:
git clone https://github.com/ConstantRobotics-Ltd/ConfigReader.git
cd ConfigReader
mkdir build
cd build
cmake ..
cmake --build .
To connect the ConfigReader library to your CMake project as source code:
If your repository has structure:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
You can add repository ConfigReader as a submodule:
cd <your repository folder>
git submodule add https://github.com/ConstantRobotics-Ltd/ConfigReader.git 3rdparty/ConfigReader
In your repository folder a directory 3rdparty/ConfigReader will be created. Alternatively, you can copy the ConfigReader repository folder into the 3rdparty folder. Repository structure:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
ConfigReader
Create a CMakeLists.txt file in the 3rdparty folder containing:
cmake_minimum_required(VERSION 3.13)
################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)
################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
add_subdirectory(ConfigReader)
Add the 3rdparty directory at the end of your main CMakeLists.txt:
add_subdirectory(3rdparty)
Link the ConfigReader library in your src/CMakeLists.txt:
target_link_libraries(${PROJECT_NAME} ConfigReader)