OmniaFramework/src/ostd/io/Json.cpp

617 lines
19 KiB
C++

#include "Json.hpp"
#include <fstream>
#include "../io/File.hpp"
#include "../io/Logger.hpp"
#include "../math/Geometry.hpp"
#include "../string/String.hpp"
namespace ostd
{
namespace detail
{
template<class T> struct Getter;
// ----- bool ---------------------------------------------------------
template<> struct Getter<bool>
{
static bool exec_impl(const std::string& p, const json& root)
{
try { return root.at(p).get<bool>(); }
catch (const json::exception&) { return false; }
}
};
// ----- i32 ----------------------------------------------------------
template<> struct Getter<i32>
{
static i32 exec_impl(const std::string& p, const json& root)
{
try { return root.at(p).get<i32>(); }
catch (const json::exception&) { return 0; }
}
};
// ----- f64 -------------------------------------------------------
template<> struct Getter<f64>
{
static f64 exec_impl(const std::string& p, const json& root)
{
try { return root.at(p).get<f64>(); }
catch (const json::exception&) { return 0.0; }
}
};
// ----- String -------------------------------------------------
template<> struct Getter<String>
{
static String exec_impl(const std::string& p, const json& root)
{
try { return String(root.at(p).get<std::string>().c_str()); }
catch (const json::exception&) { return String(""); }
}
};
// ----- Color -------------------------------------------------
template<> struct Getter<Color>
{
static Color exec_impl(const std::string& p, const json& root)
{
try {
const json& node = root.at(p);
// Case 1: Hex string ("#RRGGBBAA")
if (node.is_string())
{
const std::string& hex = node.get<std::string>();
if (hex.starts_with("#") && hex.size() == 9) // 9 for #RRGGBBAA
return Color(hex);
return Color(); // Not a valid hex format
}
// Case 2: Array of integers
if (node.is_array() && !node.empty())
{
stdvec<i32> vals;
for (const auto& v : node)
{
if (!v.is_number_integer()) return Color();
i32 iv = v.get<i32>();
if (iv < 0 || iv > 255) return Color();
vals.push_back(iv);
}
switch (vals.size())
{
case 1: return Color(vals[0], vals[0], vals[0]);
case 3: return Color(vals[0], vals[1], vals[2]);
case 4: return Color(vals[0], vals[1], vals[2], vals[3]);
default: return Color();
}
}
// Anything else → default color
return Color();
}
catch (...) { return Color(); }
}
};
// ----- Rectangle -------------------------------------------------
template<> struct Getter<Rectangle>
{
static Rectangle exec_impl(const std::string& p, const json& root)
{
try
{
const json& node = root.at(p);
if (node.is_array() && node.size() == 4)
{
stdvec<f32> vals;
vals.reserve(4);
for (const auto& v : node)
{
if (!v.is_number_float() && !v.is_number_integer())
return Rectangle();
vals.push_back(v.get<f32>());
}
return Rectangle(vals[0], vals[1], vals[2], vals[3]);
}
return Rectangle();
}
catch (...)
{
return Rectangle();
}
}
};
// ----- stdvec<i32> ----------------------------------------
template<> struct Getter<stdvec<i32>>
{
static stdvec<i32> exec_impl(const std::string& p, const json& root)
{
stdvec<i32> result;
try
{
const json& arr = root.at(p);
if (!arr.is_array()) return result;
for (const auto& v : arr)
{
if (v.is_number_integer())
result.push_back(v.get<i32>());
}
} catch (...) {}
return result;
}
};
// ----- stdvec<f64> -----------------------------------------
template<> struct Getter<stdvec<f64>>
{
static stdvec<f64> exec_impl(const std::string& p, const json& root)
{
stdvec<f64> result;
try
{
const json& arr = root.at(p);
if (!arr.is_array()) return result;
for (const auto& v : arr)
{
if (v.is_number())
result.push_back(v.get<f64>());
}
} catch (...) {}
return result;
}
};
// ----- stdvec<String> -----------------------------------
template<> struct Getter<stdvec<String>>
{
static stdvec<String> exec_impl(const std::string& p, const json& root)
{
stdvec<String> result;
try
{
const json& arr = root.at(p);
if (!arr.is_array()) return result;
for (const auto& v : arr)
{
if (v.is_string())
result.emplace_back(v.get<std::string>().c_str());
}
} catch (...) {}
return result;
}
};
// ----- stdvec<Color> -----------------------------------
template<> struct Getter<stdvec<Color>>
{
static stdvec<Color> exec_impl(const std::string& p, const json& root)
{
stdvec<Color> result;
try
{
const json& node = root.at(p);
if (!node.is_array() || node.empty())
return result;
result.reserve(node.size());
for (const auto& element : node)
{
// We temporarily create a fake "sub-root" that only contains one key "color"
// This lets us call the existing Getter<Color> without modifying it
json fake_root = { { "color", element } };
Color color = Getter<Color>::exec_impl("color", fake_root);
result.push_back(color);
}
return result;
}
catch (...)
{
return {};
}
}
};
// ----- stdvec<Vec2> -----------------------------------
template<> struct Getter<Vec2>
{
static Vec2 exec_impl(const std::string& p, const json& root)
{
try
{
const json& arr = root.at(p);
if (!arr.is_array() || arr.size() != 2)
return Vec2();
return Vec2(arr[0].get<f32>(), arr[1].get<f32>());
}
catch (...)
{
return Vec2();
}
}
};
// ----- stdvec<Rectangle> -----------------------------------
template<> struct Getter<stdvec<Rectangle>>
{
static stdvec<Rectangle> exec_impl(const std::string& p, const json& root)
{
stdvec<Rectangle> result;
try
{
const json& node = root.at(p);
if (!node.is_array() || node.empty())
return result;
result.reserve(node.size());
for (const auto& element : node)
{
json fake_root = { { "rect", element } };
Rectangle rect = Getter<Rectangle>::exec_impl("rect", fake_root);
result.push_back(rect);
}
return result;
}
catch (...)
{
return {};
}
}
};
template<class T> struct Setter;
// ----- bool ---------------------------------------------------------
template<> struct Setter<bool>
{
static bool exec_impl(const std::string& p, json& root, bool value)
{
try
{
root[p] = value;
return true;
}
catch (...) { return false; }
}
};
// ----- i32 ----------------------------------------------------------
template<> struct Setter<i32>
{
static bool exec_impl(const std::string& p, json& root, i32 value)
{
try
{
root[p] = value;
return true;
}
catch (...) { return false; }
}
};
// ----- f64 -------------------------------------------------------
template<> struct Setter<f64>
{
static bool exec_impl(const std::string& p, json& root, f64 value)
{
try
{
root[p] = value;
return true;
}
catch (...) { return false; }
}
};
// ----- String -------------------------------------------------
template<> struct Setter<String>
{
static bool exec_impl(const std::string& p, json& root, const String& value)
{
try
{
root[p] = value;
return true;
}
catch (...) { return false; }
}
};
// ----- Color -------------------------------------------------
template<> struct Setter<Color>
{
static bool exec_impl(const std::string& p, json& root, const Color& value)
{
try
{
json arr = json::array();
arr.push_back(cast<u8>(value.r));
arr.push_back(cast<u8>(value.g));
arr.push_back(cast<u8>(value.b));
if (value.a != 255) arr.push_back(cast<u8>(value.a));
root[p] = arr;
return true;
} catch (...) { return false; }
}
};
// ----- stdvec<Vec2> -----------------------------------
template<> struct Setter<Vec2>
{
static bool exec_impl(const std::string& p, json& root, const Vec2& value)
{
try
{
json arr = json::array();
arr.push_back(value.x);
arr.push_back(value.y);
root[p] = arr;
return true;
}
catch (...)
{
return false;
}
}
};
// ----- Rectangle -------------------------------------------------
template<> struct Setter<Rectangle>
{
static bool exec_impl(const std::string& p, json& root, const Rectangle& value)
{
try
{
json arr = json::array();
arr.push_back(value.x);
arr.push_back(value.y);
arr.push_back(value.w);
arr.push_back(value.h);
root[p] = arr;
return true;
}
catch (...)
{
return false;
}
}
};
// ----- stdvec<i32> ----------------------------------------
template<> struct Setter<stdvec<i32>>
{
static bool exec_impl(const std::string& p, json& root, const stdvec<i32>& value)
{
try
{
json arr = json::array();
for (i32 v : value) arr.push_back(v);
root[p] = arr;
return true;
} catch (...) { return false; }
}
};
// ----- stdvec<f64> -----------------------------------------
template<> struct Setter<stdvec<f64>>
{
static bool exec_impl(const std::string& p, json& root, const stdvec<f64>& value)
{
try
{
json arr = json::array();
for (f64 v : value) arr.push_back(v);
root[p] = arr;
return true;
} catch (...) { return false; }
}
};
// ----- stdvec<String> -----------------------------------
template<> struct Setter<stdvec<String>>
{
static bool exec_impl(const std::string& p, json& root, const stdvec<String>& value)
{
try
{
json arr = json::array();
for (const auto& s : value) arr.push_back(s.cpp_str());
root[p] = arr;
return true;
} catch (...) { return false; }
}
};
// ----- stdvec<Color> -----------------------------------
template<> struct Setter<stdvec<Color>>
{
static bool exec_impl(const std::string& p, json& root, const stdvec<Color>& value)
{
try
{
json arr = json::array();
for (const auto& color : value)
arr.push_back(color.hexString(true, "#").cpp_str());
root[p] = arr;
return true;
}
catch (...)
{
return false;
}
}
};
// ----- stdvec<Rectangle> -----------------------------------
template<> struct Setter<stdvec<Rectangle>>
{
static bool exec_impl(const std::string& p, json& root, const stdvec<Rectangle>& value)
{
try
{
json arr = json::array();
for (const auto& rect : value)
{
json rect_arr = json::array();
rect_arr.push_back(rect.x);
rect_arr.push_back(rect.y);
rect_arr.push_back(rect.w);
rect_arr.push_back(rect.h);
arr.push_back(rect_arr);
}
root[p] = arr;
return true;
}
catch (...)
{
return false;
}
}
};
}
static inline bool __navigate_json_path(const String& path, json*& outSource, String& outName)
{
auto tokens = path.tokenize(".");
if (tokens.count() <= 1)
{
if (!outSource->contains(path))
return false; // path broken
outName = path;
return true;
}
String key = "";
while (tokens.hasNext())
{
key = tokens.next();
if (!outSource->contains(key))
return false; // path broken
if (!tokens.hasNext())
{
outName = key;
return true;
}
const json& child = (*outSource)[key];
if (!child.is_object())
return false; // path broken
outSource = const_cast<json*>(&child);
}
return false;
}
template<class T>
T JsonFile::get(const String& name)
{
json* source = &m_json;
String settingName = "";
if (__navigate_json_path(name, source, settingName))
return detail::Getter<T>::exec_impl(settingName, *source);
else
OX_ERROR("Setting not found: %s", name.c_str());
return detail::Getter<T>::exec_impl(settingName, *source);
}
template<class T>
bool JsonFile::set(const String& name, T value)
{
if (!m_loaded) return false;
if (!m_writeable) return false;
auto tokens = name.tokenize(".");
if (tokens.count() == 0) return false;
json* current = &m_json;
String key;
while (tokens.hasNext())
{
key = tokens.next();
if (!tokens.hasNext()) break;
if (!current->contains(key.cpp_str()) || !(*current)[key.cpp_str()].is_object())
(*current)[key.cpp_str()] = json::object();
current = &(*current)[key.cpp_str()];
}
// Set the leaf value
if (detail::Setter<T>::exec_impl(key.cpp_str(), *current, value))
return __write_to_file();
OX_ERROR("Failed to set value for: %s", name.c_str());
return false;
}
bool JsonFile::get_bool(const String& name) { return get<bool>(name); }
i32 JsonFile::get_int(const String& name) { return get<i32>(name); }
f64 JsonFile::get_double(const String& name) { return get<f64>(name); }
String JsonFile::get_string(const String& name) { return get<String>(name); }
Color JsonFile::get_color(const String& name) { return get<Color>(name); }
Vec2 JsonFile::get_vec2(const String& name) { return get<Vec2>(name); }
Rectangle JsonFile::get_rect(const String& name) { return get<Rectangle>(name); }
stdvec<i32> JsonFile::get_int_array(const String& name) { return get<stdvec<i32>>(name); }
stdvec<f64> JsonFile::get_double_array(const String& name) { return get<stdvec<f64>>(name); }
stdvec<String> JsonFile::get_string_array(const String& name) { return get<stdvec<String>>(name); }
stdvec<Color> JsonFile::get_color_array(const String& name) { return get<stdvec<Color>>(name); }
stdvec<Rectangle> JsonFile::get_rect_array(const String& name) { return get<stdvec<Rectangle>>(name); }
bool JsonFile::set_bool(const String& name, bool value) { return set<bool>(name, value); }
bool JsonFile::set_int(const String& name, i32 value) { return set<i32>(name, value); }
bool JsonFile::set_double(const String& name, f64 value) { return set<f64>(name, value); }
bool JsonFile::set_string(const String& name, const String& value) { return set<String>(name, value); }
bool JsonFile::set_color(const String& name, const Color& value) { return set<Color>(name, value); }
bool JsonFile::set_vec2(const String& name, const Vec2& value) { return set<Vec2>(name, value); }
bool JsonFile::set_rect(const String& name, const Rectangle& value) { return set<Rectangle>(name, value); }
bool JsonFile::set_int_array(const String& name, const stdvec<i32>& value) { return set<stdvec<i32>>(name, value); }
bool JsonFile::set_double_array(const String& name, const stdvec<f64>& value) { return set<stdvec<f64>>(name, value); }
bool JsonFile::set_string_array(const String& name, const stdvec<String>& value) { return set<stdvec<String>>(name, value); }
bool JsonFile::set_color_array(const String& name, const stdvec<Color>& value) { return set<stdvec<Color>>(name, value); }
bool JsonFile::set_rect_array(const String& name, const stdvec<Rectangle>& value) { return set<stdvec<Rectangle>>(name, value); }
bool JsonFile::init(const String& filePath, bool writeable, const json* obj)
{
m_filePath = filePath;
m_rawJson = "";
m_loaded = false;
m_writeable = writeable;
if (obj != nullptr)
m_json = *obj;
ostd::TextFileBuffer jsonFile(filePath);
if (!jsonFile.exists())
{
if (!writeable)
{
OX_ERROR("JSON read-only file doesn't exist: %s", filePath.c_str());
return false;
}
m_loaded = true;
__write_to_file();
m_loaded = false;
}
jsonFile = ostd::TextFileBuffer(filePath);
try
{
m_json = json::parse(jsonFile.rawContent());
}
catch (const json::parse_error& e)
{
OX_ERROR("JSON parse failed: %s", e.what());
return false;
}
m_rawJson = jsonFile.rawContent();
m_loaded = true;
OX_DEBUG("Loaded JSON file: <%s>", filePath.c_str());
return true;
}
bool JsonFile::__write_to_file(const json* obj)
{
if (!m_loaded) return false;
if (obj == nullptr)
obj = &m_json;
try
{
std::ofstream file(m_filePath.cpp_str(), std::ios::trunc);
if (!file) throw std::runtime_error("Failed to open file.");
file << obj->dump(4);
if (file.fail()) throw std::runtime_error("Failed to write to file");
file.flush();
if (file.fail()) throw std::runtime_error("Failed to flush");
}
catch (const std::exception& e)
{
OX_ERROR("Failed to write JSON file: %s", e.what());
return false;
}
return true;
}
}