7#include <nlohmann/json.hpp>
51 [[nodiscard]] std::string
encode(
const T&
value,
int indent = 2)
53 return to_json(
value).dump(indent);
61 [[nodiscard]]
bool write(
const std::string& path,
const T&
value,
int indent = 2)
64 std::ofstream file(path, std::ios::out | std::ios::trunc);
65 if (!file.is_open()) {
66 m_last_error =
"Failed to open for writing: " + path;
69 file << encode(
value, indent);
71 m_last_error =
"Write failed: " + path;
87 [[nodiscard]] std::optional<T>
decode(
const std::string& str)
91 auto j = nlohmann::json::parse(str);
95 }
catch (
const std::exception& e) {
96 m_last_error = std::string(
"decode error: ") + e.what();
106 template <
typename T>
107 [[nodiscard]] std::optional<T>
read(
const std::string& path)
109 m_last_error.clear();
110 const auto resolved = resolve_path(path);
111 std::ifstream file(resolved);
112 if (!file.is_open()) {
113 m_last_error =
"Failed to open for reading: " + resolved;
117 auto j = nlohmann::json::parse(file);
121 }
catch (
const std::exception& e) {
122 m_last_error = std::string(
"read error in ") + resolved +
": " + e.what();
130 [[nodiscard]]
const std::string&
last_error()
const {
return m_last_error; }
135 [[nodiscard]]
static std::string
resolve_path(
const std::string& filepath)
137 namespace fs = std::filesystem;
144 auto from_cwd = fs::current_path() /
normalized;
145 if (fs::exists(from_cwd))
146 return from_cwd.string();
147 auto from_root = fs::path(Config::SOURCE_DIR) /
normalized;
148 if (fs::exists(from_root))
149 return from_root.string();
157 template <
typename T>
161 nlohmann::json j = nlohmann::json::object();
163 [&](
const auto&... props) {
164 (encode_property(j, val, props), ...);
168 }
else if constexpr (Reflect::is_optional_v<T>) {
169 if (!val.has_value()) {
172 return to_json(*val);
173 }
else if constexpr (Reflect::is_vector_v<T>) {
174 auto arr = nlohmann::json::array();
175 for (
const auto& item : val) {
176 arr.push_back(to_json(item));
179 }
else if constexpr (Reflect::is_string_map_v<T>) {
180 nlohmann::json j = nlohmann::json::object();
181 for (
const auto& [
k, v] : val) {
185 }
else if constexpr (std::is_enum_v<T>) {
186 return static_cast<std::underlying_type_t<T>
>(val);
187 }
else if constexpr (std::is_same_v<T, nlohmann::json>) {
195 template <
typename Class,
typename T>
204 template <
typename Class,
typename T>
210 const auto& opt = obj.*prop.
member;
211 if (opt.has_value()) {
212 j[prop.
key] = to_json(*opt);
220 template <
typename T>
224 if (!j.is_object()) {
225 throw nlohmann::json::type_error::create(302,
"expected object for Reflectable type", &j);
228 [&](
const auto&... props) {
229 (decode_property(j, out, props), ...);
232 }
else if constexpr (Reflect::is_optional_v<T>) {
239 out = std::move(inner);
241 }
else if constexpr (Reflect::is_vector_v<T>) {
244 throw nlohmann::json::type_error::create(302,
"expected array", &j);
246 out.reserve(j.size());
247 for (
const auto& item : j) {
249 from_json(item, element);
250 out.push_back(std::move(element));
253 }
else if constexpr (Reflect::is_string_map_v<T>) {
256 throw nlohmann::json::type_error::create(302,
"expected object for map", &j);
258 for (
const auto& [
k, v] : j.items()) {
261 out.emplace(
k, std::move(val));
263 }
else if constexpr (std::is_enum_v<T>) {
264 out =
static_cast<T>(j.get<std::underlying_type_t<T>>());
266 }
else if constexpr (std::is_same_v<T, nlohmann::json>) {
274 template <
typename Class,
typename T>
276 const nlohmann::json& j,
280 if (j.contains(prop.
key)) {
281 from_json(j.at(prop.
key), obj.*prop.
member);
285 template <
typename Class,
typename T>
287 const nlohmann::json& j,
291 if (!j.contains(prop.
key) || j.at(prop.
key).is_null()) {
292 obj.*prop.
member = std::nullopt;
295 from_json(j.at(prop.
key), inner);
296 obj.*prop.
member = std::move(inner);
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::vector< double > normalized(const std::vector< double > &data, double target_peak)
Normalize single-channel data (non-destructive)
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.