7#include <nlohmann/json.hpp>
52 [[nodiscard]] std::string
encode(
const T& value,
int indent = 2)
54 return to_json(value).dump(indent);
62 [[nodiscard]]
bool write(
const std::string& path,
const T& value,
int indent = 2)
65 std::ofstream file(path, std::ios::out | std::ios::trunc);
66 if (!file.is_open()) {
67 m_last_error =
"Failed to open for writing: " + path;
70 file << encode(value, indent);
72 m_last_error =
"Write failed: " + path;
88 [[nodiscard]] std::optional<T>
decode(
const std::string& str)
92 auto j = nlohmann::json::parse(str);
96 }
catch (
const std::exception& e) {
97 m_last_error = std::string(
"decode error: ") + e.what();
107 template <
typename T>
108 [[nodiscard]] std::optional<T>
read(
const std::string& path)
110 m_last_error.clear();
111 const auto resolved = FileReader::resolve_path(path);
112 std::ifstream file(resolved);
113 if (!file.is_open()) {
114 m_last_error =
"Failed to open for reading: " + resolved;
118 auto j = nlohmann::json::parse(file);
122 }
catch (
const std::exception& e) {
123 m_last_error = std::string(
"read error in ") + resolved +
": " + e.what();
131 [[nodiscard]]
const std::string&
last_error()
const {
return m_last_error; }
140 template <
typename T>
144 nlohmann::json j = nlohmann::json::object();
146 [&](
const auto&... props) {
147 (encode_property(j, val, props), ...);
151 }
else if constexpr (is_optional_v<T>) {
152 if (!val.has_value()) {
155 return to_json(*val);
156 }
else if constexpr (is_vector_v<T>) {
157 auto arr = nlohmann::json::array();
158 for (
const auto& item : val) {
159 arr.push_back(to_json(item));
162 }
else if constexpr (is_string_map_v<T>) {
163 nlohmann::json j = nlohmann::json::object();
164 for (
const auto& [k, v] : val) {
169 return encode_glm(val);
170 }
else if constexpr (std::is_enum_v<T>) {
171 return static_cast<std::underlying_type_t<T>
>(val);
177 template <
typename Class,
typename T>
186 template <
typename Class,
typename T>
192 const auto& opt = obj.*prop.
member;
193 if (opt.has_value()) {
194 j[prop.
key] = to_json(*opt);
202 template <
typename T>
206 if (!j.is_object()) {
207 throw nlohmann::json::type_error::create(
208 302,
"expected object for Reflectable type", &j);
211 [&](
const auto&... props) {
212 (decode_property(j, out, props), ...);
215 }
else if constexpr (is_optional_v<T>) {
222 out = std::move(inner);
224 }
else if constexpr (is_vector_v<T>) {
227 throw nlohmann::json::type_error::create(302,
"expected array", &j);
230 out.reserve(j.size());
231 for (
const auto& item : j) {
233 from_json(item, element);
234 out.push_back(std::move(element));
236 }
else if constexpr (is_string_map_v<T>) {
238 if (!j.is_object()) {
239 throw nlohmann::json::type_error::create(302,
"expected object for map", &j);
242 for (
const auto& [k, v] : j.items()) {
245 out.emplace(k, std::move(val));
249 }
else if constexpr (std::is_enum_v<T>) {
250 out =
static_cast<T>(j.get<std::underlying_type_t<T>>());
256 template <
typename Class,
typename T>
258 const nlohmann::json& j,
262 if (j.contains(prop.
key)) {
263 from_json(j.at(prop.
key), obj.*prop.
member);
267 template <
typename Class,
typename T>
269 const nlohmann::json& j,
273 if (!j.contains(prop.
key) || j.at(prop.
key).is_null()) {
274 obj.*prop.
member = std::nullopt;
277 from_json(j.at(prop.
key), inner);
278 obj.*prop.
member = std::move(inner);
286 template <
typename T>
290 constexpr auto n = glm_component_count<T>();
291 using Comp = glm_component_type<T>;
292 auto arr = nlohmann::json::array();
293 const Comp* ptr = &v[0];
294 for (
size_t i = 0; i < n; ++i) {
295 arr.push_back(ptr[i]);
300 template <
typename T>
305 throw nlohmann::json::type_error::create(302,
"expected array for glm type", &j);
307 constexpr auto n = glm_component_count<T>();
309 throw nlohmann::json::other_error::create(
310 501,
"glm component count mismatch", &j);
312 using Comp = glm_component_type<T>;
314 for (
size_t i = 0; i < n; ++i) {
315 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.