9#include <nlohmann/json.hpp>
53 [[nodiscard]] std::string
encode(
const T&
value,
int indent = 2)
55 return to_json(
value).dump(indent);
63 [[nodiscard]]
bool write(
const std::string& path,
const T&
value,
int indent = 2)
67 std::ofstream file(resolved, std::ios::out | std::ios::trunc);
68 if (!file.is_open()) {
69 m_last_error =
"Failed to open for writing: " + resolved;
72 file << encode(
value, indent);
74 m_last_error =
"Write failed: " + resolved;
90 [[nodiscard]] std::optional<T>
decode(
const std::string& str)
94 auto j = nlohmann::json::parse(str);
98 }
catch (
const std::exception& e) {
99 m_last_error = std::string(
"decode error: ") + e.what();
109 template <
typename T>
110 [[nodiscard]] std::optional<T>
read(
const std::string& path)
112 m_last_error.clear();
113 const auto resolved = resolve_path(path);
114 std::ifstream file(resolved);
115 if (!file.is_open()) {
116 m_last_error =
"Failed to open for reading: " + resolved;
120 auto j = nlohmann::json::parse(file);
124 }
catch (
const std::exception& e) {
125 m_last_error = std::string(
"read error in ") + resolved +
": " + e.what();
133 [[nodiscard]]
const std::string&
last_error()
const {
return m_last_error; }
138 [[nodiscard]]
static std::string
resolve_path(
const std::string& filepath)
140 namespace fs = std::filesystem;
141 auto normalized = std::string(filepath);
142 std::ranges::replace(normalized,
'\\',
'/');
143 if (fs::path(normalized).is_absolute())
145 if (fs::exists(normalized))
147 auto from_cwd = fs::current_path() / normalized;
148 if (fs::exists(from_cwd))
149 return from_cwd.string();
150 auto from_root = fs::path(Config::project_root()) / normalized;
151 if (fs::exists(from_root))
152 return from_root.string();
160 template <
typename T>
164 nlohmann::json j = nlohmann::json::object();
166 [&](
const auto&... props) {
167 (encode_property(j, val, props), ...);
171 }
else if constexpr (Reflect::is_optional_v<T>) {
172 if (!val.has_value()) {
175 return to_json(*val);
176 }
else if constexpr (Reflect::is_vector_v<T>) {
177 auto arr = nlohmann::json::array();
178 for (
const auto& item : val) {
179 arr.push_back(to_json(item));
182 }
else if constexpr (Reflect::is_string_map_v<T>) {
183 nlohmann::json j = nlohmann::json::object();
184 for (
const auto& [k, v] : val) {
188 }
else if constexpr (std::is_enum_v<T>) {
189 return static_cast<std::underlying_type_t<T>
>(val);
190 }
else if constexpr (std::is_same_v<T, nlohmann::json>) {
198 template <
typename Class,
typename T>
207 template <
typename Class,
typename T>
213 const auto& opt = obj.*prop.
member;
214 if (opt.has_value()) {
215 j[prop.
key] = to_json(*opt);
223 template <
typename T>
227 if (!j.is_object()) {
228 throw nlohmann::json::type_error::create(302,
"expected object for Reflectable type", &j);
231 [&](
const auto&... props) {
232 (decode_property(j,
out, props), ...);
235 }
else if constexpr (Reflect::is_optional_v<T>) {
242 out = std::move(inner);
244 }
else if constexpr (Reflect::is_vector_v<T>) {
247 throw nlohmann::json::type_error::create(302,
"expected array", &j);
249 out.reserve(j.size());
250 for (
const auto& item : j) {
252 from_json(item, element);
253 out.push_back(std::move(element));
256 }
else if constexpr (Reflect::is_string_map_v<T>) {
259 throw nlohmann::json::type_error::create(302,
"expected object for map", &j);
261 for (
const auto& [k, v] : j.items()) {
264 out.emplace(k, std::move(val));
266 }
else if constexpr (std::is_enum_v<T>) {
267 out =
static_cast<T>(j.get<std::underlying_type_t<T>>());
269 }
else if constexpr (std::is_same_v<T, nlohmann::json>) {
277 template <
typename Class,
typename T>
279 const nlohmann::json& j,
283 if (j.contains(prop.
key)) {
284 from_json(j.at(prop.
key), obj.*prop.
member);
288 template <
typename Class,
typename T>
290 const nlohmann::json& j,
294 if (!j.contains(prop.
key) || j.at(prop.
key).is_null()) {
295 obj.*prop.
member = std::nullopt;
298 from_json(j.at(prop.
key), inner);
299 obj.*prop.
member = std::move(inner);
std::vector< float > * out
std::string encode(const T &value, int indent=2)
Serialize value to a JSON string.
JSONSerializer & operator=(const JSONSerializer &)=delete
static void encode_property(nlohmann::json &j, const Class &obj, const Reflect::OptionalProperty< Class, T > &prop)
std::optional< T > read(const std::string &path)
Read path and deserialize into T.
JSONSerializer(const JSONSerializer &)=delete
static void from_json(const nlohmann::json &j, T &out)
std::optional< T > decode(const std::string &str)
Parse str and deserialize into T.
const std::string & last_error() const
Last error message, empty if no error.
static void encode_property(nlohmann::json &j, const Class &obj, const Reflect::Property< Class, T > &prop)
static std::string resolve_path(const std::string &filepath)
bool write(const std::string &path, const T &value, int indent=2)
Encode value and write to path (created or truncated).
JSONSerializer(JSONSerializer &&)=default
static void decode_property(const nlohmann::json &j, Class &obj, const Reflect::Property< Class, T > &prop)
static void decode_property(const nlohmann::json &j, Class &obj, const Reflect::OptionalProperty< Class, T > &prop)
static nlohmann::json to_json(const T &val)
JSONSerializer & operator=(JSONSerializer &&)=default
Converts arbitrary C++ types to/from JSON strings and disk files.
std::string resolve_write_path(const std::string &filepath, std::string_view default_directory=Config::OUT_DIR)
Resolve a writable path against the configured output directory.
Extension point for JSONSerializer.
std::optional< T > Class::* member
Binds a string key to a std::optional<T> member pointer.
Binds a string key to a required member pointer.