cppfig 0.1.0
Modern C++20 compile-time type-safe configuration library
Loading...
Searching...
No Matches
value.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <map>
5#include <memory>
6#include <sstream>
7#include <string>
8#include <string_view>
9#include <type_traits>
10#include <variant>
11#include <vector>
12
13#include "cppfig/status.h"
14
15namespace cppfig {
16
26class Value {
27public:
29 using ObjectType = std::map<std::string, Value, std::less<>>;
30
32 using ArrayType = std::vector<Value>;
33
34private:
35 using DataVariant = std::variant<std::nullptr_t, bool, std::int64_t, double, std::string, std::shared_ptr<ObjectType>, std::shared_ptr<ArrayType>>;
36
37 DataVariant data_;
38
39 static constexpr std::size_t idx_null = 0;
40 static constexpr std::size_t idx_bool = 1;
41 static constexpr std::size_t idx_int = 2;
42 static constexpr std::size_t idx_double = 3;
43 static constexpr std::size_t idx_string = 4;
44 static constexpr std::size_t idx_object = 5;
45 static constexpr std::size_t idx_array = 6;
46
47public:
50 : data_(nullptr)
51 {
52 }
53
55 Value(std::nullptr_t) // NOLINT(google-explicit-constructor)
56 : data_(nullptr)
57 {
58 }
59
61 Value(bool b) // NOLINT(google-explicit-constructor)
62 : data_(b)
63 {
64 }
65
67 Value(int i) // NOLINT(google-explicit-constructor)
68 : data_(static_cast<std::int64_t>(i))
69 {
70 }
71
73 Value(std::int64_t i) // NOLINT(google-explicit-constructor)
74 : data_(i)
75 {
76 }
77
79 Value(double d) // NOLINT(google-explicit-constructor)
80 : data_(d)
81 {
82 }
83
85 Value(float f) // NOLINT(google-explicit-constructor)
86 : data_(static_cast<double>(f))
87 {
88 }
89
91 Value(const char* s) // NOLINT(google-explicit-constructor)
92 : data_(std::string(s))
93 {
94 }
95
97 Value(std::string s) // NOLINT(google-explicit-constructor)
98 : data_(std::move(s))
99 {
100 }
101
103 Value(std::string_view s) // NOLINT(google-explicit-constructor)
104 : data_(std::string(s))
105 {
106 }
107
109 Value(const Value& other)
110 : data_(DeepCopy(other.data_))
111 {
112 }
113
115 auto operator=(const Value& other) -> Value&
116 {
117 if (this != &other) {
118 data_ = DeepCopy(other.data_);
119 }
120 return *this;
121 }
122
124 Value(Value&&) noexcept = default;
125
127 auto operator=(Value&&) noexcept -> Value& = default;
128
130 ~Value() = default;
131
133 [[nodiscard]] static auto Object() -> Value
134 {
135 Value v;
136 v.data_ = std::make_shared<ObjectType>();
137 return v;
138 }
139
141 [[nodiscard]] static auto Array() -> Value
142 {
143 Value v;
144 v.data_ = std::make_shared<ArrayType>();
145 return v;
146 }
147
149 [[nodiscard]] auto IsNull() const -> bool { return data_.index() == idx_null; }
150
152 [[nodiscard]] auto IsBoolean() const -> bool { return data_.index() == idx_bool; }
153
155 [[nodiscard]] auto IsInteger() const -> bool { return data_.index() == idx_int; }
156
158 [[nodiscard]] auto IsDouble() const -> bool { return data_.index() == idx_double; }
159
161 [[nodiscard]] auto IsNumber() const -> bool { return IsInteger() || IsDouble(); }
162
164 [[nodiscard]] auto IsString() const -> bool { return data_.index() == idx_string; }
165
167 [[nodiscard]] auto IsObject() const -> bool { return data_.index() == idx_object; }
168
170 [[nodiscard]] auto IsArray() const -> bool { return data_.index() == idx_array; }
171
177 template <typename T>
178 [[nodiscard]] auto Get() const -> T
179 {
180 if constexpr (std::is_same_v<T, bool>) {
181 return std::get<bool>(data_);
182 }
183 else if constexpr (std::is_same_v<T, int>) {
184 return static_cast<int>(std::get<std::int64_t>(data_));
185 }
186 else if constexpr (std::is_same_v<T, std::int64_t>) {
187 return std::get<std::int64_t>(data_);
188 }
189 else if constexpr (std::is_same_v<T, double>) {
190 if (IsInteger()) {
191 return static_cast<double>(std::get<std::int64_t>(data_));
192 }
193 return std::get<double>(data_);
194 }
195 else if constexpr (std::is_same_v<T, float>) {
196 if (IsInteger()) {
197 return static_cast<float>(std::get<std::int64_t>(data_));
198 }
199 return static_cast<float>(std::get<double>(data_));
200 }
201 else if constexpr (std::is_same_v<T, std::string>) {
202 return std::get<std::string>(data_);
203 }
204 else {
205 static_assert(sizeof(T) == 0, "Unsupported type for Value::Get<T>()");
206 }
207 }
208
210 [[nodiscard]] auto Contains(std::string_view key) const -> bool
211 {
212 if (!IsObject()) {
213 return false;
214 }
215 const auto& obj = *std::get<std::shared_ptr<ObjectType>>(data_);
216 return obj.find(key) != obj.end();
217 }
218
220 auto operator[](const std::string& key) -> Value&
221 {
222 if (!IsObject()) {
223 data_ = std::make_shared<ObjectType>();
224 }
225 auto& obj = *std::get<std::shared_ptr<ObjectType>>(data_);
226 return obj[key];
227 }
228
230 auto operator[](const std::string& key) const -> const Value&
231 {
232 static const Value null_value;
233 if (!IsObject()) {
234 return null_value;
235 }
236 const auto& obj = *std::get<std::shared_ptr<ObjectType>>(data_);
237 auto iter = obj.find(key);
238 if (iter == obj.end()) {
239 return null_value;
240 }
241 return iter->second;
242 }
243
245 [[nodiscard]] auto Items() const -> const ObjectType&
246 {
247 static const ObjectType empty;
248 if (!IsObject()) {
249 return empty;
250 }
251 return *std::get<std::shared_ptr<ObjectType>>(data_);
252 }
253
256 auto Items() -> ObjectType&
257 {
258 if (!IsObject()) {
259 data_ = std::make_shared<ObjectType>();
260 }
261 return *std::get<std::shared_ptr<ObjectType>>(data_);
262 }
263
265 [[nodiscard]] auto GetAtPath(std::string_view path) const -> StatusOr<Value>
266 {
267 const Value* current = this;
268 std::string path_str(path);
269 std::istringstream stream(path_str);
270 std::string segment;
271
272 while (std::getline(stream, segment, '.')) {
273 if (!current->IsObject()) {
274 return NotFoundError("Path segment '" + segment + "' not found: parent is not an object");
275 }
276 if (!current->Contains(segment)) {
277 return NotFoundError("Path segment '" + segment + "' not found");
278 }
279 current = &(*current)[segment];
280 }
281 return *current;
282 }
283
285 void SetAtPath(std::string_view path, const Value& value)
286 {
287 Value* current = this;
288 std::string path_str(path);
289 std::istringstream stream(path_str);
290 std::string segment;
291 std::vector<std::string> segments;
292
293 while (std::getline(stream, segment, '.')) {
294 segments.push_back(segment);
295 }
296
297 for (std::size_t i = 0; i < segments.size() - 1; ++i) {
298 if (!current->Contains(segments[i]) || !(*current)[segments[i]].IsObject()) {
299 (*current)[segments[i]] = Value::Object();
300 }
301 current = &(*current)[segments[i]];
302 }
303
304 if (!segments.empty()) {
305 (*current)[segments.back()] = value;
306 }
307 }
308
310 [[nodiscard]] auto HasPath(std::string_view path) const -> bool { return GetAtPath(path).ok(); }
311
316 [[nodiscard]] static auto Merge(const Value& base, const Value& overlay) -> Value
317 {
318 if (!base.IsObject() || !overlay.IsObject()) {
319 return overlay;
320 }
321
322 Value result = base;
323 for (const auto& [key, value] : overlay.Items()) {
324 if (result.Contains(key) && result[key].IsObject() && value.IsObject()) {
325 result[key] = Merge(result[key], value);
326 }
327 else {
328 result[key] = value;
329 }
330 }
331 return result;
332 }
333
337 [[nodiscard]] auto Dump(int indent = 0) const -> std::string
338 {
339 std::ostringstream stream;
340 DumpImpl(stream, indent, 0);
341 return stream.str();
342 }
343
345 auto operator==(const Value& other) const -> bool
346 {
347 if (data_.index() != other.data_.index()) {
348 return false;
349 }
350 if (IsNull()) {
351 return true;
352 }
353 if (IsBoolean()) {
354 return Get<bool>() == other.Get<bool>();
355 }
356 if (IsInteger()) {
357 return Get<std::int64_t>() == other.Get<std::int64_t>();
358 }
359 if (IsDouble()) {
360#pragma GCC diagnostic push
361#pragma GCC diagnostic ignored "-Wfloat-equal"
362 return Get<double>() == other.Get<double>();
363#pragma GCC diagnostic pop
364 }
365 if (IsString()) {
366 return Get<std::string>() == other.Get<std::string>();
367 }
368 if (IsObject()) {
369 return Items() == other.Items();
370 }
371 if (IsArray()) {
372 return *std::get<std::shared_ptr<ArrayType>>(data_) == *std::get<std::shared_ptr<ArrayType>>(other.data_);
373 }
374 return false; // LCOV_EXCL_LINE
375 }
376
378 auto operator!=(const Value& other) const -> bool { return !(*this == other); }
379
380private:
381 [[nodiscard]] static auto DeepCopy(const DataVariant& src) -> DataVariant
382 {
383 if (auto* obj = std::get_if<std::shared_ptr<ObjectType>>(&src)) {
384 return std::make_shared<ObjectType>(**obj);
385 }
386 if (auto* arr = std::get_if<std::shared_ptr<ArrayType>>(&src)) {
387 return std::make_shared<ArrayType>(**arr);
388 }
389 return src;
390 }
391
392 static void EscapeString(std::ostringstream& stream, const std::string& str)
393 {
394 stream << '"';
395 for (char ch : str) {
396 switch (ch) {
397 case '"':
398 stream << "\\\"";
399 break;
400 case '\\':
401 stream << "\\\\";
402 break;
403 case '\n':
404 stream << "\\n";
405 break;
406 case '\r':
407 stream << "\\r";
408 break;
409 case '\t':
410 stream << "\\t";
411 break;
412 default:
413 stream << ch;
414 }
415 }
416 stream << '"';
417 }
418
419 static void WriteIndent(std::ostringstream& stream, int indent, int depth)
420 {
421 if (indent > 0) {
422 stream << '\n';
423 for (int i = 0; i < indent * depth; ++i) {
424 stream << ' ';
425 }
426 }
427 }
428
429 void DumpImpl(std::ostringstream& stream, int indent, int depth) const
430 {
431 if (IsNull()) {
432 stream << "null";
433 }
434 else if (IsBoolean()) {
435 stream << (Get<bool>() ? "true" : "false");
436 }
437 else if (IsInteger()) {
438 stream << Get<std::int64_t>();
439 }
440 else if (IsDouble()) {
441 std::ostringstream double_stream;
442 double_stream << Get<double>();
443 auto str = double_stream.str();
444 stream << str;
445 // Ensure there is always a decimal point for clarity
446 if (str.find('.') == std::string::npos && str.find('e') == std::string::npos && str.find('E') == std::string::npos) {
447 stream << ".0";
448 }
449 }
450 else if (IsString()) {
451 EscapeString(stream, Get<std::string>());
452 }
453 else if (IsObject()) {
454 const auto& obj = Items();
455 stream << '{';
456 bool first = true;
457 for (const auto& [key, val] : obj) {
458 if (!first) {
459 stream << ',';
460 }
461 first = false;
462 WriteIndent(stream, indent, depth + 1);
463 if (indent > 0) {
464 // no space needed after newline+indent
465 }
466 stream << '"' << key << '"' << ':';
467 if (indent > 0) {
468 stream << ' ';
469 }
470 val.DumpImpl(stream, indent, depth + 1);
471 }
472 if (!obj.empty()) {
473 WriteIndent(stream, indent, depth);
474 }
475 stream << '}';
476 }
477 else if (IsArray()) {
478 const auto& arr = *std::get<std::shared_ptr<ArrayType>>(data_);
479 stream << '[';
480 bool first = true;
481 for (const auto& val : arr) {
482 if (!first) {
483 stream << ',';
484 }
485 first = false;
486 WriteIndent(stream, indent, depth + 1);
487 val.DumpImpl(stream, indent, depth + 1);
488 }
489 if (!arr.empty()) {
490 WriteIndent(stream, indent, depth);
491 }
492 stream << ']';
493 }
494 }
495};
496
497} // namespace cppfig
A value-or-error type, similar to std::expected (C++23).
Definition status.h:100
A self-contained, recursive value type for configuration data.
Definition value.h:26
auto operator=(const Value &other) -> Value &
Deep-copy assignment.
Definition value.h:115
Value(std::nullptr_t)
Constructs a null value.
Definition value.h:55
auto GetAtPath(std::string_view path) const -> StatusOr< Value >
Gets a value at a dot-separated path.
Definition value.h:265
auto operator==(const Value &other) const -> bool
Value equality (deep comparison for objects/arrays).
Definition value.h:345
auto IsBoolean() const -> bool
Returns true if this value is a boolean.
Definition value.h:152
auto Items() const -> const ObjectType &
Returns const reference to the object entries.
Definition value.h:245
Value()
Constructs a null value.
Definition value.h:49
static auto Merge(const Value &base, const Value &overlay) -> Value
Deep-merges two object values; overlay takes precedence.
Definition value.h:316
Value(const Value &other)
Deep-copies the value (recursive for objects / arrays).
Definition value.h:109
auto operator!=(const Value &other) const -> bool
Value inequality.
Definition value.h:378
auto Items() -> ObjectType &
Returns mutable reference to the object entries, promoting null → object.
Definition value.h:256
auto Contains(std::string_view key) const -> bool
Checks whether the given key exists in an object value.
Definition value.h:210
Value(int i)
Constructs an integer value from int.
Definition value.h:67
auto IsNumber() const -> bool
Returns true if this value is any numeric type (integer or double).
Definition value.h:161
Value(std::string_view s)
Constructs a string value from string_view.
Definition value.h:103
Value(std::int64_t i)
Constructs an integer value.
Definition value.h:73
auto IsObject() const -> bool
Returns true if this value is an object (key-value map).
Definition value.h:167
std::map< std::string, Value, std::less<> > ObjectType
Ordered map of string keys to Value children.
Definition value.h:29
static auto Array() -> Value
Creates an empty array value.
Definition value.h:141
auto IsNull() const -> bool
Returns true if this value is null.
Definition value.h:149
auto IsDouble() const -> bool
Returns true if this value is a double.
Definition value.h:158
Value(bool b)
Constructs a boolean value.
Definition value.h:61
void SetAtPath(std::string_view path, const Value &value)
Sets a value at a dot-separated path, creating intermediate objects.
Definition value.h:285
auto HasPath(std::string_view path) const -> bool
Checks if a path exists in the data.
Definition value.h:310
Value(Value &&) noexcept=default
Move constructor (default, transfers ownership).
Value(std::string s)
Constructs a string value.
Definition value.h:97
std::vector< Value > ArrayType
Ordered sequence of Value elements.
Definition value.h:32
static auto Object() -> Value
Creates an empty object value.
Definition value.h:133
auto IsInteger() const -> bool
Returns true if this value is an integer.
Definition value.h:155
auto IsString() const -> bool
Returns true if this value is a string.
Definition value.h:164
Value(const char *s)
Constructs a string value from a C string.
Definition value.h:91
auto IsArray() const -> bool
Returns true if this value is an array.
Definition value.h:170
auto Dump(int indent=0) const -> std::string
Produces a JSON-like string representation.
Definition value.h:337
Value(float f)
Constructs a double value from float.
Definition value.h:85
auto operator[](const std::string &key) -> Value &
Accesses or creates a child by key, promoting null → object.
Definition value.h:220
Value(double d)
Constructs a double value.
Definition value.h:79
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:230
auto Get() const -> T
Extracts the stored value as the requested type.
Definition value.h:178
C++20 compile-time type-safe configuration library.
Definition conf.h:13
auto NotFoundError(std::string message) -> Status
Returns a NotFound error status.
Definition status.h:55