I want to make an attempt to write my own simple RPG. As a preparation for this task I created a class which I could use to store/retrieve my in-game objects data to/from disc. I chose JSON format as it is very simple and has good library support in C++.While I was working on it, I thought that it could be useful to have other data formats (YAML? XML? INI?), but I decided to leave it for the future. I want to polish what I have now, before I move on to extending DataManager possibilities.Class definition:#ifndef DATAMANAGER_HPP#define DATAMANAGER_HPP#include <filesystem>#include <iosfwd>#include <map>#include <json.hpp>//Two user defined literals which might be useful for defining//objects of class path and directory_entry from <filesystem>std::experimental::filesystem::path operator_p(const char* p, std::size_t len);std::experimental::filesystem::directory_entry operator_d(const char* d, std::size_t len);namespace utils{    //both added for convenience    namespace fs = std::experimental::filesystem;    using json = nlohmann::json;    //Class handling data description file I/O    class DataManager    {        //as operator<< requires access to private data, it's declared friend        friend std::ostream& operator<<(std::ostream&, DataManager const&);    public:        //No Parameter Constructor.        //Sets default directory where data will be stored        //to working dir/data.        DataManager();        //Constructor with filesystem::path parameter.        //Sets default directory where data will be stored        //to data_dir.        DataManager(fs::path data_dir);        //Trivial destructor.        ~DataManager();        //This function clears DataManager object.        //Only data held by it is cleared.        void clear() { m_data.clear(); m_is_empty = true; }        //Retrieves data by obj_id.        //Data is returned in json format.        json get(std::string obj_id, std::string file = settings) const;        //Retrieves data by obj_id.        //Data is returned in std::string which contains json data destription.        std::string get_str(std::string obj_id, std::string file = settings) const;        //Adds data for object with obj_id to file.        void add(std::string obj_id, json data, std::string file = settings);        void add(std::string obj_id, std::string json_str, std::string file = settings);        //Three functions which read data from files (either single,        //or a several from one directory) and stores them in internal map.        //If no file_location is supplied, default data dir is used.        //If file_location does not exist, no action is taken.        //file_loaction can be specified either as absolute path,        //of relatively to default data directory.        //If read will be called twice, for two files with the same name,        //but in two different directories, the files will be effectively MERGED as far as        //internal data storage is concerned (which means that write function will        //store them in the same file!).        void read(std::vector<std::string> files, fs::path file_location = fs::path{});        void read(std::vector<fs::path> files, fs::path file_location = fs::path{});        void read(fs::path file, fs::path file_location = fs::path{});        //Reads all files (with proper format) from selected directory.        //Directory can be specified either as absolute path,        //of relatively to default data directory.         //Searching for files can be recursively or not.        //Files relative loaction to data directory is kept.        //This means that function write will recreate directory        //structure relative to output directory.        void read_dir(std::string directory, bool recursive = true);        void read_dir(fs::directory_entry directory, bool recursive = true);        //Stores all data from map on HDD.        //If data_dir is specified it is used as root        //for all files and subdirectories.        //If no data_dir is given, function uses        //data directory set by constructor.        //Directory can be specified either as absolute path,        //of relatively to default data directory.         void write(fs::path data_dir = fs::path{}) const;        //Returns parent path (absolute) for data directory.        std::string root_path() const { return m_data_dir.path().parent_path().generic_string(); }        //Returns absolute path to directory where data will be stored to/read from        //unless specified otherwise.        std::string data_dir() const { return m_data_dir.path().filename().generic_string(); }        //Checks if there is any data in internal storage.        bool is_empty() const noexcept { return m_is_empty; }    private:        //internal functions - code reuse        //or shortening other functions        void read_impl(fs::path&& file_name);        void read_recursive(fs::path dir);        void read_non_recursive(fs::path dir);        void write_impl(fs::path file, std::ofstream& out_file) const;        bool m_is_empty = true;        //Multimap which stores all data passed to DataManager        std::map <std::string, json> m_data;        //Default data directory. Set during object construction.        fs::directory_entry m_data_dir;        //This member is used in write function        //so it has to be mutable        //It's only internal helper variable.        //Its mutability is not relevant for users.        //It's goal is to keep track of current I/O directory,        //so that we could store only relative paths in map.        mutable fs::path m_tmp_data_dir = fs::path{};        //extension of the files used for storage        std::string m_file_ext = .json;    };}#endif //DATAMANAGER_HPPClass implementation:#include DataManager.hpp#include <fstream>std::experimental::filesystem::path operator_p(const char* p, std::size_t len){    return std::experimental::filesystem::path{ p };}std::experimental::filesystem::directory_entry operator_d(const char* d, std::size_t len){    return std::experimental::filesystem::directory_entry{ d };}namespace utils{    namespace    {        //MSVC 2015 lacks some functions in <filesystem> so I implemented        //very simple substitute that just gets job done        fs::path lexically_relative(fs::path data_dir, fs::path root_dir)        {            std::string result = data_dir.generic_string().substr(root_dir.generic_string().length());            if (result.front() == '\\' || result.front() == '/')                result = result.substr(1);            return fs::path{ result };        }    }    DataManager::DataManager() :DataManager(fs::current_path() / fs::path{ data })    { }    DataManager::DataManager(fs::path data_dir)    {        data_dir = data_dir.is_relative() ? fs::canonical(data_dir, fs::current_path()) : data_dir;        m_data_dir = fs::directory_entry{ data_dir };    }    DataManager::~DataManager()    {    }    json DataManager::get(std::string obj_id, std::string file) const    {        if (m_is_empty || (m_data.count(file) == 0))            return json{};        return m_data.at(file)[obj_id];    }    std::string DataManager::get_str(std::string obj_id, std::string file) const    {        return get(obj_id, file).dump();    }    void DataManager::add(std::string obj_id, json data, std::string file)    {        m_data[file] += json::object_t::value_type(obj_id, data);        m_is_empty = false;    }    void DataManager::add(std::string obj_id, std::string json_str, std::string file)    {        add(obj_id, json::parse(json_str.c_str()), file);    }    void DataManager::read(fs::path file, fs::path file_location)    {        file_location = file_location.empty() ? m_data_dir.path() : file_location;        file_location = file_location.is_relative() ? fs::canonical(file_location, m_data_dir.path()) : file_location;        if (!fs::exists(file_location))            return;        read_impl(file_location / file.replace_extension(m_file_ext));    }    void DataManager::read(std::vector<std::string> files, fs::path files_location)    {        files_location = files_location.empty() ? m_data_dir.path() : files_location;        files_location = files_location.is_relative() ? fs::canonical(files_location, m_data_dir.path()) : files_location;        if (!fs::exists(files_location))            return;        for (auto file : files)        {            read_impl(files_location / fs::path{ file }.replace_extension(m_file_ext));        }    }    void DataManager::read(std::vector<fs::path> files, fs::path files_location)    {        files_location = files_location.empty() ? m_data_dir.path() : files_location;        files_location = files_location.is_relative() ? fs::canonical(files_location, m_data_dir.path()) : files_location;        if (!fs::exists(files_location))            return;        for (auto file : files)        {            read_impl(files_location / file.replace_extension(m_file_ext));        }    }    void DataManager::read_dir(std::string directory, bool recursive)    {        read_dir(fs::directory_entry(directory), recursive);    }    void DataManager::read_dir(fs::directory_entry directory, bool recursive)    {        fs::path dir = directory.path().is_relative() ? fs::canonical(directory, m_data_dir.path().parent_path()) : directory;        if (!fs::exists(dir))            return;        if (recursive)        {            m_tmp_data_dir = fs::canonical(directory,m_data_dir.path().parent_path());            read_recursive(dir);            m_tmp_data_dir = fs::path{};        }        else        {            read_non_recursive(dir);        }    }    void DataManager::read_impl(fs::path&& file_name)    {        json tmp;        std::ifstream in_file(file_name, std::ifstream::in);        in_file >> tmp;        in_file.close();        file_name.replace_extension();        if (!m_tmp_data_dir.empty())        {            file_name = lexically_relative(file_name, m_tmp_data_dir);        }        else        {            file_name = file_name.filename();        }        std::string input_file = file_name.generic_string();        if (input_file.front() == '\\' || input_file.front() == '/')    {        input_file = input_file.substr(1);    }        for (json::iterator it = tmp.begin(); it != tmp.end(); ++it)        {            add(it.key(), it.value(), input_file);        }        m_is_empty = false;    }    void DataManager::read_non_recursive(fs::path dir)    {        fs::directory_iterator dir_it{ dir };        for (auto& item : dir_it)        {            fs::path tmp = item.path();            if (tmp.extension() == m_file_ext)            {                read(tmp.filename().generic_string(), tmp.parent_path());            }        }    }    void DataManager::read_recursive(fs::path dir)    {        fs::recursive_directory_iterator rdir_it{ dir };        for (auto& item : rdir_it)        {            fs::path tmp = item.path();            if (tmp.extension() == m_file_ext)            {                read(tmp.filename().generic_string(), tmp.parent_path());            }        }    }    void DataManager::write_impl(fs::path file, std::ofstream & out_file) const    {        auto data = m_data.at(lexically_relative(file, m_tmp_data_dir).generic_string());        out_file.open(file.replace_extension(m_file_ext), std::ofstream::out);        out_file << std::setw(4);        out_file << data;        out_file.close();    }    void DataManager::write(fs::path data_dir) const    {        fs::path dir = data_dir.empty() ? m_data_dir.path() : data_dir;        dir = dir.is_relative() ? fs::canonical(dir, m_data_dir.path().parent_path()) : dir;        m_tmp_data_dir = dir;        if (!fs::exists(dir))        {            fs::create_directories(dir);        }        std::ofstream file;        for (auto item : m_data)        {            fs::path target = dir / fs::path(item.first);            if (!fs::exists(target.parent_path()))            {                fs::create_directories(target.parent_path());            }            write_impl(target, file);        }        m_tmp_data_dir = fs::path{};    }    std::ostream & operator<<(std::ostream & out, DataManager const & dm)    {        for (auto item : dm.m_data)        {            out << item.first << :\n;            out << item.second << \n;        }        return out;    }}Example of usage:#include DataManager.hppint main(){    utils::json test_json = R({        id:0,        val : {            obj1 : {                name:obj1,                nums : [1,2,3]            },            obj2 : {                name:obj2,                nums : [4,5,6]            }        }    })_json;    std::string test_str = R({        id:0,        val : {            obj1 : {                name:obj1,                nums : [1,2,3]            },            obj2 : {                name:obj2,                nums : [4,5,6]            }        }    });    utils::DataManager dm(../my_data);    dm.add(test_obj1, test_json);    dm.add(test_obj2, test_str, subdir/test_file);    dm.write();    return 0;}The code should create this:-some_dir    -/working_dir        -program.exe    -/my_data        -/subdir            -test_file.json        -settings.jsonEdit:I used this json library:https://github.com/nlohmann/json
Class for managing (file I/O) data
c++;json;file system;c++17