I'm sure this is a topic that is dealt quite a lot, but couldn't find my answer.
I'd like to construct an object based on a value parsed from a JSON file.
For example, a "class doubleBuffer" which gets its size from a configuration file.
But this object resides as a member in another class (class client), which by the time it parses the JSON, the doubleBuffer field in it is already constructed.
class doubleBuffer {
int _size = 0;
uint8_t _buf = nullptr;
doubleBuffer(int size) : _size(size) {
_buf = new (uint8_t[_size]);
}
doubleBuffer() {}
}
class client {
doubleBuffer _db;
int _size = 0;
public:
void client() {
readConfiguration(); // _size is set from the configuration file
}
void readConfiguration() { ... }
}
int main() {
client cl;
// here, the configuration file is already parsed,
// but the _db field has been constructed with a default ctor.
// And what I'd like to happen is to construct it from the size,
// parsed in the configuration file.
return 0;
}
How would you recommend to solve this problem?
CodePudding user response:
Read the JSON file to a JSON object.
Pass the object as an argument to the constructor of client
. Fetch branches of the json and pass them down to subobjects' constructors, like this:
class client {
int _size;
doubleBuffer _db;
public:
client (const JSON& config) :
_size(config.get<int>("size")),
_db(_size) { ... }
};
JSON config;
config.read(".config.json");
client cl(config);
(I invented the JSON
class API on the fly, use whatever your library provides)
CodePudding user response:
You have 2 choices:
parse the JSON before you construct the
client
. Pass the parsed result into theclient
's constructor, and then let it delegate the parsed values to member constructors as needed.Have
client
parse the JSON as you are, but then construct thedoubleBuffer
dynamically after the parsing is finished. Or, default-construct thedoubleBuffer
, and then add a method todoubleBuffer
so you can resize its_buf
dynamically after the JSON is parsed.
CodePudding user response:
I'd rework it a bit. You probably don't want to read the configuration every time you instantiate a client
- or re-read the configuration for any other object that needs some parameter from the configuration file. Read it once and use it many times.
With most Json libraries you can also add parsers for your own types, which makes it easy to get the data out into a format that doesn't need to be parsed and re-parsed every time you need something from it. Here's an example using the Json for Modern C library.
Say your config.json
looks like this:
{
"application": "My soft config",
"db_buf_size": 2048
}
And you want this available to your application stored in a
struct config_data {
std::string application;
std::size_t db_buf_size;
};
You'd simply add a from_json
function to make the library able to parse it:
void from_json(const nlohmann::json& obj, config_data& c) {
c.application = obj.at("application").get<std::string>();
c.db_buf_size = obj.at("db_buf_size").get<std::size_t>();
}
You also need a function that you can call at any time that returns a reference to the loaded configuration and here's an example of a function named config()
that returns a reference to a config_data
. Since the config_data
instance is static
it will only read and parse the configuration from file once, no matter how many calls to config()
you do:
#include <cerrno>
#include <cstring>
#include <filesystem>
#include <stdexcept>
const config_data& config() {
static const config_data conf = [] {
std::filesystem::path path = "config.json";
std::ifstream ifs(path);
if(!ifs)
throw std::runtime_error(path.string() ": "
std::strerror(errno));
auto j = json::parse(ifs);
return j.get<config_data>(); // using the user defined parser
}();
return conf;
}
You can now let the client
constructor call config()
and fetch any value from the configuration. Again, since the actual config_data
instance in the config()
function is static
, actually loading and parsing the data will only done once.
class doubleBuffer {
public:
doubleBuffer(size_t size) : m_size(size), m_buf{new uint8_t[m_size]} {}
doubleBuffer(const doubleBuffer&) = delete;
doubleBuffer(doubleBuffer&&) = delete;
~doubleBuffer() { delete[] m_buf; }
size_t size() const { return m_size; }
private:
size_t m_size = 0;
uint8_t* m_buf = nullptr;
};
class client {
public:
client() : m_db(config().db_buf_size) {} // fetch the needed data
size_t size() const { return m_db.size(); }
private:
doubleBuffer m_db;
};
You may also want to consider using a std::vector<uint8_t>
instead of a raw uint8_t*
.