617 lines
19 KiB
C++
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;
|
|
}
|
|
}
|