9#include <nlohmann/json.hpp>
54 [[nodiscard]] std::string
encode(
const T& value,
int indent = 2)
56 return to_json(value).dump(indent);
64 [[nodiscard]]
bool write(
const std::string& path,
const T& value,
int indent = 2)
67 std::ofstream file(path, std::ios::out | std::ios::trunc);
68 if (!file.is_open()) {
69 m_last_error =
"Failed to open for writing: " + path;
72 file << encode(value, indent);
74 m_last_error =
"Write failed: " + path;
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 = FileReader::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; }
142 template <
typename T>
146 nlohmann::json j = nlohmann::json::object();
148 [&](
const auto&... props) {
149 (encode_property(j, val, props), ...);
153 }
else if constexpr (is_optional_v<T>) {
154 if (!val.has_value()) {
157 return to_json(*val);
158 }
else if constexpr (is_vector_v<T>) {
159 auto arr = nlohmann::json::array();
160 for (
const auto& item : val) {
161 arr.push_back(to_json(item));
164 }
else if constexpr (is_string_map_v<T>) {
165 nlohmann::json j = nlohmann::json::object();
166 for (
const auto& [k, v] : val) {
171 return encode_glm(val);
172 }
else if constexpr (std::is_enum_v<T>) {
173 return static_cast<std::underlying_type_t<T>
>(val);
174 }
else if constexpr (std::is_same_v<T, nlohmann::json>) {
181 template <
typename Class,
typename T>
190 template <
typename Class,
typename T>
196 const auto& opt = obj.*prop.
member;
197 if (opt.has_value()) {
198 j[prop.
key] = to_json(*opt);
206 template <
typename T>
210 if (!j.is_object()) {
211 auto e = nlohmann::json::type_error::create(
212 302,
"expected object for Reflectable type", &j);
213 error<std::runtime_error>(
214 Journal::Component::IO,
215 Journal::Context::Runtime,
216 std::source_location::current(),
217 "JSON type error: {}", e.what());
220 [&](
const auto&... props) {
221 (decode_property(j, out, props), ...);
224 }
else if constexpr (is_optional_v<T>) {
231 out = std::move(inner);
233 }
else if constexpr (is_vector_v<T>) {
236 auto e = nlohmann::json::type_error::create(302,
"expected array", &j);
237 error<std::runtime_error>(
238 Journal::Component::IO,
239 Journal::Context::Runtime,
240 std::source_location::current(),
241 "JSON type error: {}", e.what());
244 out.reserve(j.size());
245 for (
const auto& item : j) {
247 from_json(item, element);
248 out.push_back(std::move(element));
250 }
else if constexpr (is_string_map_v<T>) {
252 if (!j.is_object()) {
253 auto e = nlohmann::json::type_error::create(302,
"expected object for map", &j);
254 error<std::runtime_error>(
255 Journal::Component::IO,
256 Journal::Context::Runtime,
257 std::source_location::current(),
258 "JSON type error: {}", e.what());
261 for (
const auto& [k, v] : j.items()) {
264 out.emplace(k, std::move(val));
268 }
else if constexpr (std::is_enum_v<T>) {
269 out =
static_cast<T>(j.get<std::underlying_type_t<T>>());
270 }
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);
307 template <
typename T>
311 constexpr auto n = glm_component_count<T>();
312 using Comp = glm_component_type<T>;
313 auto arr = nlohmann::json::array();
314 const Comp*
ptr = &v[0];
315 for (
size_t i = 0; i < n; ++i) {
316 arr.push_back(
ptr[i]);
321 template <
typename T>
326 auto e = nlohmann::json::type_error::create(302,
"expected array for glm type", &j);
327 error<std::runtime_error>(
328 Journal::Component::IO,
329 Journal::Context::Runtime,
330 std::source_location::current(),
331 "JSON type error: {}", e.what());
333 constexpr auto n = glm_component_count<T>();
335 auto e = nlohmann::json::other_error::create(
336 501,
"glm component count mismatch", &j);
337 error<std::runtime_error>(
338 Journal::Component::IO,
339 Journal::Context::Runtime,
340 std::source_location::current(),
341 "JSON error: expected {} components for type {}, got {}: {}",
342 n,
typeid(
T).name(), j.size(), e.what());
344 using Comp = glm_component_type<T>;
346 for (
size_t i = 0; i < n; ++i) {
347 ptr[i] = j[i].get<Comp>();
std::string encode(const T &value, int indent=2)
Serialize value to a JSON string.
static nlohmann::json encode_glm(const T &v)
JSONSerializer & operator=(const JSONSerializer &)=delete
std::optional< T > read(const std::string &path)
Read path and deserialize into T.
JSONSerializer(const JSONSerializer &)=delete
static void encode_property(nlohmann::json &j, const Class &obj, const OptionalProperty< Class, T > &prop)
static void from_json(const nlohmann::json &j, T &out)
std::optional< T > decode(const std::string &str)
Parse str and deserialize into T.
static void encode_property(nlohmann::json &j, const Class &obj, const Property< Class, T > &prop)
static void decode_glm(const nlohmann::json &j, T &out)
const std::string & last_error() const
Last error message, empty if no error.
static void decode_property(const nlohmann::json &j, Class &obj, const Property< Class, T > &prop)
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 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::optional< T > Class::* member
Binds a string key to a std::optional<T> member pointer.
Binds a string key to a required member pointer.