35 using ObjectType = std::map<std::string, Value, std::less<>>;
41 using DataVariant = std::variant<std::nullptr_t, bool, std::int64_t, double, std::string, std::unique_ptr<ObjectType>, std::unique_ptr<ArrayType>>;
45 static constexpr std::size_t idx_null = 0;
46 static constexpr std::size_t idx_bool = 1;
47 static constexpr std::size_t idx_int = 2;
48 static constexpr std::size_t idx_double = 3;
49 static constexpr std::size_t idx_string = 4;
50 static constexpr std::size_t idx_object = 5;
51 static constexpr std::size_t idx_array = 6;
74 : data_(static_cast<std::int64_t>(i))
92 : data_(static_cast<double>(f))
98 : data_(std::string(s))
104 : data_(std::move(s))
110 : data_(std::string(s))
116 : data_(DeepCopy(other.data_))
123 if (
this != &other) {
124 data_ = DeepCopy(other.data_);
142 v.data_ = std::make_unique<ObjectType>();
150 v.data_ = std::make_unique<ArrayType>();
155 [[nodiscard]]
auto IsNull() const ->
bool {
return data_.index() == idx_null; }
158 [[nodiscard]]
auto IsBoolean() const ->
bool {
return data_.index() == idx_bool; }
161 [[nodiscard]]
auto IsInteger() const ->
bool {
return data_.index() == idx_int; }
164 [[nodiscard]]
auto IsDouble() const ->
bool {
return data_.index() == idx_double; }
170 [[nodiscard]]
auto IsString() const ->
bool {
return data_.index() == idx_string; }
173 [[nodiscard]]
auto IsObject() const ->
bool {
return data_.index() == idx_object; }
176 [[nodiscard]]
auto IsArray() const ->
bool {
return data_.index() == idx_array; }
183 template <
typename T>
184 [[nodiscard]]
auto Fits() const ->
bool
186 if constexpr (std::is_same_v<T, bool>) {
189 else if constexpr (std::is_same_v<T, int>) {
193 const auto stored = std::get<std::int64_t>(data_);
194 return stored >= std::numeric_limits<int>::min() && stored <= std::numeric_limits<int>::max();
196 else if constexpr (std::is_same_v<T, std::int64_t>) {
199 else if constexpr (std::is_same_v<T, double>) {
202 else if constexpr (std::is_same_v<T, float>) {
209 const auto stored = std::get<double>(data_);
211 return !std::isfinite(stored) || (stored >= -
static_cast<double>(std::numeric_limits<float>::max()) && stored <=
static_cast<double>(std::numeric_limits<float>::max()));
213 else if constexpr (std::is_same_v<T, std::string>) {
217 static_assert(
sizeof(T) == 0,
"Unsupported type for Value::Fits<T>()");
233 template <
typename T>
234 [[nodiscard]]
auto Get() const -> T
236 if constexpr (std::is_same_v<T, bool>) {
237 return std::get<bool>(data_);
239 else if constexpr (std::is_same_v<T, int>) {
240 const auto stored = std::get<std::int64_t>(data_);
242 throw std::out_of_range(
"Value " + std::to_string(stored) +
" does not fit in int");
244 return static_cast<int>(stored);
246 else if constexpr (std::is_same_v<T, std::int64_t>) {
247 return std::get<std::int64_t>(data_);
249 else if constexpr (std::is_same_v<T, double>) {
251 return static_cast<double>(std::get<std::int64_t>(data_));
253 return std::get<double>(data_);
255 else if constexpr (std::is_same_v<T, float>) {
257 return static_cast<float>(std::get<std::int64_t>(data_));
259 const auto stored = std::get<double>(data_);
260 if (!Fits<float>()) {
261 throw std::out_of_range(
"Value " + std::to_string(stored) +
" does not fit in float");
263 return static_cast<float>(stored);
265 else if constexpr (std::is_same_v<T, std::string>) {
266 return std::get<std::string>(data_);
269 static_assert(
sizeof(T) == 0,
"Unsupported type for Value::Get<T>()");
278 [[nodiscard]]
auto TryToText() const -> std::optional<std::
string>
281 return std::get<bool>(data_) ?
"true" :
"false";
284 return std::to_string(std::get<std::int64_t>(data_));
287 std::ostringstream stream;
288 stream << std::get<double>(data_);
292 return std::get<std::string>(data_);
303 template <
typename T>
304 [[nodiscard]]
auto TryGet() const -> std::optional<T>
306 if constexpr (std::is_same_v<T, bool>) {
311 else if constexpr (std::is_same_v<T, int> || std::is_same_v<T, std::int64_t>) {
316 else if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) {
321 else if constexpr (std::is_same_v<T, std::string>) {
327 static_assert(
sizeof(T) == 0,
"Unsupported type for Value::TryGet<T>()");
333 [[nodiscard]]
auto Contains(std::string_view key)
const ->
bool
338 const auto& obj = *std::get<std::unique_ptr<ObjectType>>(data_);
339 return obj.find(key) != obj.end();
346 data_ = std::make_unique<ObjectType>();
348 auto& obj = *std::get<std::unique_ptr<ObjectType>>(data_);
355 static const Value null_value;
359 const auto& obj = *std::get<std::unique_ptr<ObjectType>>(data_);
360 auto iter = obj.find(key);
361 if (iter == obj.end()) {
374 return *std::get<std::unique_ptr<ObjectType>>(data_);
382 data_ = std::make_unique<ObjectType>();
384 return *std::get<std::unique_ptr<ObjectType>>(data_);
394 return *std::get<std::unique_ptr<ArrayType>>(data_);
402 data_ = std::make_unique<ArrayType>();
404 return *std::get<std::unique_ptr<ArrayType>>(data_);
413 [[nodiscard]]
static auto SplitPath(std::string_view path) -> std::vector<std::string>
415 std::vector<std::string> segments;
420 std::string path_str(path);
421 std::istringstream stream(path_str);
424 while (std::getline(stream, segment,
'.')) {
425 if (segment.empty()) {
428 segments.push_back(segment);
432 if (path.back() ==
'.') {
445 if (segments.empty()) {
446 return NotFoundError(
"Ill-formed configuration path: '" + std::string(path) +
"'");
449 const Value* current =
this;
450 for (
const auto& segment : segments) {
452 return NotFoundError(
"Path segment '" + segment +
"' not found: parent is not an object");
455 return NotFoundError(
"Path segment '" + segment +
"' not found");
457 current = &(*current)[segment];
469 if (segments.empty()) {
473 Value* current =
this;
474 for (std::size_t i = 0; i + 1 < segments.size(); ++i) {
475 if (!current->
Contains(segments[i]) || !(*current)[segments[i]].IsObject()) {
478 current = &(*current)[segments[i]];
481 (*current)[segments.back()] = value;
485 [[nodiscard]]
auto HasPath(std::string_view path)
const ->
bool {
return GetAtPath(path).ok(); }
493 if (!base.IsObject() || !overlay.IsObject()) {
498 for (
const auto& [key, value] : overlay.Items()) {
500 result[key] =
Merge(result[key], value);
512 [[nodiscard]]
auto Dump(
int indent = 0) const -> std::
string
514 std::ostringstream stream;
515 DumpImpl(stream, indent, 0);
522 if (data_.index() != other.data_.index()) {
529 return Get<bool>() == other.Get<
bool>();
532 return Get<std::int64_t>() == other.Get<std::int64_t>();
535#pragma GCC diagnostic push
536#pragma GCC diagnostic ignored "-Wfloat-equal"
537 return Get<double>() == other.Get<
double>();
538#pragma GCC diagnostic pop
541 return Get<std::string>() == other.Get<std::string>();
544 return Items() == other.Items();
547 return *std::get<std::unique_ptr<ArrayType>>(data_) == *std::get<std::unique_ptr<ArrayType>>(other.data_);
556 [[nodiscard]]
static auto DeepCopy(
const DataVariant& src) -> DataVariant
559 [](
const auto& held) -> DataVariant {
560 using Held = std::decay_t<
decltype(held)>;
561 if constexpr (std::is_same_v<Held, std::unique_ptr<ObjectType>>) {
563 return held ? std::make_unique<ObjectType>(*held) : std::make_unique<
ObjectType>();
565 else if constexpr (std::is_same_v<Held, std::unique_ptr<ArrayType>>) {
566 return held ? std::make_unique<ArrayType>(*held) : std::make_unique<
ArrayType>();
575 static void EscapeString(std::ostringstream& stream,
const std::string& str)
578 for (
char ch : str) {
602 static void WriteIndent(std::ostringstream& stream,
int indent,
int depth)
606 for (
int i = 0; i < indent * depth; ++i) {
612 void DumpImpl(std::ostringstream& stream,
int indent,
int depth)
const
618 stream << (Get<bool>() ?
"true" :
"false");
621 stream << Get<std::int64_t>();
624 std::ostringstream double_stream;
625 double_stream << Get<double>();
626 auto str = double_stream.str();
629 if (str.find(
'.') == std::string::npos && str.find(
'e') == std::string::npos && str.find(
'E') == std::string::npos) {
634 EscapeString(stream, Get<std::string>());
637 const auto& obj =
Items();
640 for (
const auto& [key, val] : obj) {
645 WriteIndent(stream, indent, depth + 1);
649 stream <<
'"' << key <<
'"' <<
':';
653 val.DumpImpl(stream, indent, depth + 1);
656 WriteIndent(stream, indent, depth);
661 const auto& arr = *std::get<std::unique_ptr<ArrayType>>(data_);
664 for (
const auto& val : arr) {
669 WriteIndent(stream, indent, depth + 1);
670 val.DumpImpl(stream, indent, depth + 1);
673 WriteIndent(stream, indent, depth);
A value-or-error type, similar to std::expected (C++23).
Definition status.h:102
A self-contained, recursive value type for configuration data.
Definition value.h:32
auto operator=(const Value &other) -> Value &
Deep-copy assignment.
Definition value.h:121
Value(std::nullptr_t)
Constructs a null value.
Definition value.h:61
auto GetAtPath(std::string_view path) const -> StatusOr< Value >
Gets a value at a dot-separated path.
Definition value.h:442
auto operator==(const Value &other) const -> bool
Value equality (deep comparison for objects/arrays).
Definition value.h:520
auto IsBoolean() const -> bool
Returns true if this value is a boolean.
Definition value.h:158
auto Items() const -> const ObjectType &
Returns const reference to the object entries.
Definition value.h:368
Value()
Constructs a null value.
Definition value.h:55
static auto Merge(const Value &base, const Value &overlay) -> Value
Deep-merges two object values; overlay takes precedence.
Definition value.h:491
Value(const Value &other)
Deep-copies the value (recursive for objects / arrays).
Definition value.h:115
auto operator!=(const Value &other) const -> bool
Value inequality.
Definition value.h:553
auto Items() -> ObjectType &
Returns mutable reference to the object entries, promoting null → object.
Definition value.h:379
auto Contains(std::string_view key) const -> bool
Checks whether the given key exists in an object value.
Definition value.h:333
Value(int i)
Constructs an integer value from int.
Definition value.h:73
auto Fits() const -> bool
Returns true if the stored value is representable as T.
Definition value.h:184
auto IsNumber() const -> bool
Returns true if this value is any numeric type (integer or double).
Definition value.h:167
Value(std::string_view s)
Constructs a string value from string_view.
Definition value.h:109
Value(std::int64_t i)
Constructs an integer value.
Definition value.h:79
auto IsObject() const -> bool
Returns true if this value is an object (key-value map).
Definition value.h:173
std::map< std::string, Value, std::less<> > ObjectType
Ordered map of string keys to Value children.
Definition value.h:35
auto Elements() const -> const ArrayType &
Returns const reference to the array elements.
Definition value.h:388
static auto Array() -> Value
Creates an empty array value.
Definition value.h:147
auto IsNull() const -> bool
Returns true if this value is null.
Definition value.h:155
auto IsDouble() const -> bool
Returns true if this value is a double.
Definition value.h:164
Value(bool b)
Constructs a boolean value.
Definition value.h:67
void SetAtPath(std::string_view path, const Value &value)
Sets a value at a dot-separated path, creating intermediate objects.
Definition value.h:466
auto HasPath(std::string_view path) const -> bool
Checks if a path exists in the data.
Definition value.h:485
Value(Value &&) noexcept=default
Move constructor (default, transfers ownership).
auto TryToText() const -> std::optional< std::string >
Renders a scalar value as text, or nullopt for objects/arrays.
Definition value.h:278
Value(std::string s)
Constructs a string value.
Definition value.h:103
std::vector< Value > ArrayType
Ordered sequence of Value elements.
Definition value.h:38
static auto Object() -> Value
Creates an empty object value.
Definition value.h:139
auto IsInteger() const -> bool
Returns true if this value is an integer.
Definition value.h:161
auto IsString() const -> bool
Returns true if this value is a string.
Definition value.h:170
auto TryGet() const -> std::optional< T >
Extracts the stored value as T, or nullopt if it is not one.
Definition value.h:304
Value(const char *s)
Constructs a string value from a C string.
Definition value.h:97
auto IsArray() const -> bool
Returns true if this value is an array.
Definition value.h:176
auto Dump(int indent=0) const -> std::string
Produces a JSON-like string representation.
Definition value.h:512
Value(float f)
Constructs a double value from float.
Definition value.h:91
auto operator[](const std::string &key) -> Value &
Accesses or creates a child by key, promoting null → object.
Definition value.h:343
Value(double d)
Constructs a double value.
Definition value.h:85
static auto SplitPath(std::string_view path) -> std::vector< std::string >
Splits a dot-separated path into its segments.
Definition value.h:413
auto operator[](const std::string &key) const -> const Value &
Read-only access to a child by key (returns static null for missing keys).
Definition value.h:353
auto Get() const -> T
Extracts the stored value as the requested type.
Definition value.h:234
auto Elements() -> ArrayType &
Returns mutable reference to the array elements, promoting null → array.
Definition value.h:399
C++20 compile-time type-safe configuration library.
Definition conf.h:17
auto NotFoundError(std::string message) -> Status
Returns a NotFound error status.
Definition status.h:57