it was difficult to find a precise title for the question, so if you have a better nameing, please help me out!
I am working on my program configuration with json, where I read hundreds of values from a json file with differnt file types (bool, string, integer, ...)
As it is right now, I have a save AND a read function and manually have to take care of the correct json-keys in BOTH of them. To improve it, I want to merge the two functions and just pass a parameter whether the program should save or read the values. My special requirement is, that I dont want to have redundant keys. So I dont want to use the json key or the variable name twice.
Here an example for a merged solution but still with the problem of double naming (redundancy):
int memory_variable = 0;
Json::Value config;
enum config_access{
WRITE,
READ
};
void handle_config(config_access mode) {
switch (mode) {
case config_access::READ:
config["test_value"] == memory_variable;
break;
case config_access::WRITE:
memory_variable= config["test_value"].asInt();
break;
}
}
Does anybody has a smart idea how I could read or write from or to the local variable with one condition parameter at the top?
Note: I am using the JsonCpp from GitHub (https://github.com/open-source-parsers/jsoncpp)
Minimal reproducible code
#include <json/json.h>
int memory_variable = 0;
Json::Value config;
enum config_access {
WRITE,
READ
};
int load_json_file(std::string path, Json::Value& json_object) {
std::string rawJson;
std::ifstream file(path);
if (file.fail()) {
return EXIT_FAILURE;
}
std::string line;
while (getline(file, line)) {
rawJson = line "\n";
}
const auto rawJsonLength = static_cast<int>(rawJson.length());
JSONCPP_STRING err;
Json::CharReaderBuilder builder;
const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (!reader->parse(rawJson.c_str(), rawJson.c_str() rawJsonLength, &json_object, &err)) {
std::cout << err << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
void handle_config(config_access mode) {
switch (mode) {
case config_access::READ:
config["test_value"] == memory_variable;
break;
case config_access::WRITE:
memory_variable = config["test_value"].asInt();
break;
}
}
int main()
{
load_json_file("C:/config.json", config);
handle_config(config_access::READ);
}
CodePudding user response:
One way to make changes to the format easier to maintain is to store the config as a Json::Value
and to provide getters and setters to it. To simplify reading/writing to files, add overloads to operator<<
and operator>>
.
Example:
#include "json/json.h"
#include <fstream>
#include <iostream>
class Config {
public:
// setters and getters
void set_memory_variable(Json::Value::Int val) {
json_value["memory_variable"] = val;
}
Json::Value::Int get_memory_variable() const {
return json_value["memory_variable"].asInt();
}
private:
Json::Value json_value; // storing the internal configuration
friend std::istream& operator>>(std::istream& is, Config& conf) {
// read configuration from any istream
try {
is >> conf.json_value;
} catch(const std::exception& ex) {
is.setstate(std::ios::failbit);
std::clog << ex.what() << std::endl; // debug log
}
return is;
}
friend std::ostream& operator<<(std::ostream& os, const Config& conf) {
// stream out all the config here
return os << conf.json_value;
}
};
Example usage:
config.json
{
"memory_variable" : 123
}
int main() {
if(std::ifstream file("config.json"); file) { // open config file
if(Config config; file >> config) { // read config
int val = config.get_memory_variable();
val *= 2;
config.set_memory_variable(val); // update the internal value
std::cout << config << '\n'; // write config to any ostream
}
}
}
CodePudding user response:
Rather than merging reading and writing into a single function, I'd probably just use variables for the keys you're using, and have both the functions for reading and writing use those variables for the keys.
static char const *testKey = "test_value";
// ...
int readKey() {
return config[testKey].asInt();
}
void writeKey(int value) {
config[testKey] = value;
}
Alternatively, you might consider creating a class for a key:
class intKey {
std::string_view keyName;
public:
key(std::string_view keyName) : keyName(keyName) {}
key &operator=(int value) {
config[keyName] = value;
return *this;
}
operator int() const {
return config[keyName].asInt();
}
};
In the latter case, you'd create an object, and reading from or writing to that key would look like reading or writing any other int
:
intKey configValue("TestKey");
// write
configValue = 1;
// read
int i = configValue;
Of course, it doesn't hurt to use both of these together, using a variable for the name of the key you supply to create the object (e.g., intkey configValue(testKey);
).