Home > OS >  C read or overwrite variable depending on parameter
C read or overwrite variable depending on parameter

Time:03-23

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);).

  • Related