Serialization: Adding Serialization for MFA

This commit will add the persistency of Dbus properties related to MFA.
The configuration file will be stored under
/var/lib/usr_mgr.conf.

Change-Id: Ib7fdc467c7cb094d328ae670df3bb4352e4a7b91
Signed-off-by: Abhilash Raju <abhilash.kollam@gmail.com>
diff --git a/json_serializer.hpp b/json_serializer.hpp
new file mode 100644
index 0000000..81a6a4d
--- /dev/null
+++ b/json_serializer.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <format>
+#include <fstream>
+#include <ranges>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+class JsonSerializer
+{
+  public:
+    JsonSerializer(std::string path, nlohmann::json js = nlohmann::json()) :
+        mfaConfPath(path), jsonData(std::move(js))
+    {}
+
+    inline auto stringSplitter()
+    {
+        return std::views::split('/') | std::views::transform([](auto&& sub) {
+                   return std::string(sub.begin(), sub.end());
+               });
+    }
+    nlohmann::json makeJson(const std::string& key, const std::string& value)
+    {
+        auto keys = key | stringSplitter();
+        std::vector v(keys.begin(), keys.end());
+        auto rv = v | std::views::reverse;
+        nlohmann::json init;
+        init[rv.front()] = value;
+        auto newJson = std::reduce(rv.begin() + 1, rv.end(), init,
+                                   [](auto sofar, auto currentKey) {
+                                       nlohmann::json j;
+                                       j[currentKey] = sofar;
+                                       return j;
+                                   });
+        return newJson;
+    }
+    std::optional<nlohmann::json> getLeafNode(const std::string_view keyPath)
+    {
+        auto keys = keyPath | stringSplitter();
+        nlohmann::json current = jsonData;
+        for (auto key : keys)
+        {
+            if (!current.contains(key))
+            {
+                return std::nullopt;
+            }
+            current = current[key];
+        }
+        return current;
+    }
+    void serialize(std::string key, const std::string value)
+    {
+        jsonData.merge_patch(makeJson(key, value));
+    }
+    template <typename T>
+    void deserialize(std::string key, T& value)
+    {
+        auto leaf = getLeafNode(key);
+        if (leaf)
+        {
+            value = *leaf;
+        }
+    }
+    void erase(std::string key)
+    {
+        if (jsonData.contains(key))
+        {
+            jsonData.erase(key);
+        }
+    }
+    bool store()
+    {
+        std::filesystem::path dir =
+            std::filesystem::path(mfaConfPath).parent_path();
+
+        // Check if the directory exists, and create it if it does not
+        if (!dir.string().empty() && !std::filesystem::exists(dir))
+        {
+            std::error_code ec;
+            if (!std::filesystem::create_directories(dir, ec))
+            {
+                lg2::error("Unable to create directory {DIR}", "DIR",
+                           dir.string());
+                return false;
+            }
+        }
+        std::ofstream file(mfaConfPath.data());
+        if (file.is_open())
+        {
+            file << jsonData.dump(4); // Pretty print with 4 spaces
+            file.close();
+            return true;
+        }
+        else
+        {
+            lg2::error("Unable to open file {FILENAME}", "FILENAME",
+                       mfaConfPath);
+            return false;
+        }
+    }
+    void load()
+    {
+        std::ifstream file(mfaConfPath.data());
+
+        if (file.is_open())
+        {
+            file >> jsonData;
+            file.close();
+        }
+        else
+        {
+            lg2::error("Unable to open file for reading {FILENAME}", "FILENAME",
+                       mfaConfPath);
+        }
+    }
+
+  private:
+    const std::string mfaConfPath;
+    nlohmann::json jsonData;
+};