metadata 0.2.0
Modern C++23 header-only metadata container (JSON-like)
Loading...
Searching...
No Matches
value.hpp
1#pragma once
2
3#include <md/error.hpp>
4
5#include <concepts>
6#include <cstdint>
7#include <initializer_list>
8#include <memory>
9#include <string>
10#include <string_view>
11#include <type_traits>
12#include <variant>
13#include <vector>
14
15namespace md {
16
17class Value;
18class Object;
19
21using Array = std::vector<Value>;
22
23namespace detail {
24
25// Transparent hash + equal-to lets std::unordered_map<std::string, ...> accept
26// std::string_view / const char* in find/contains/erase without allocating.
27struct TransparentStringHash {
28 using is_transparent = void;
29
30 [[nodiscard]] std::size_t operator()(const std::string_view sv) const noexcept {
31 return std::hash<std::string_view>{}(sv);
32 }
33 [[nodiscard]] std::size_t operator()(const std::string& s) const noexcept {
34 return std::hash<std::string_view>{}(s);
35 }
36 [[nodiscard]] std::size_t operator()(const char* s) const noexcept {
37 return std::hash<std::string_view>{}(std::string_view{s});
38 }
39};
40
41// Concepts that exclude bool and character types so that Value{true} stays
42// a bool and Value{'a'} doesn't sneak in as an integer.
43template <class T>
44concept SignedIntLike =
45 std::is_integral_v<T> && std::is_signed_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool> &&
46 !std::is_same_v<std::remove_cv_t<T>, char> && !std::is_same_v<std::remove_cv_t<T>, wchar_t> &&
47 !std::is_same_v<std::remove_cv_t<T>, char8_t> &&
48 !std::is_same_v<std::remove_cv_t<T>, char16_t> &&
49 !std::is_same_v<std::remove_cv_t<T>, char32_t>;
50
51template <class T>
52concept UnsignedIntLike =
53 std::is_integral_v<T> && std::is_unsigned_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool> &&
54 !std::is_same_v<std::remove_cv_t<T>, char> && !std::is_same_v<std::remove_cv_t<T>, wchar_t> &&
55 !std::is_same_v<std::remove_cv_t<T>, char8_t> &&
56 !std::is_same_v<std::remove_cv_t<T>, char16_t> &&
57 !std::is_same_v<std::remove_cv_t<T>, char32_t>;
58
59template <class T>
60concept FloatLike = std::is_floating_point_v<T>;
61
62} // namespace detail
63
67class Value {
68public:
69 // Object is indirected through unique_ptr so Value can recursively hold an
70 // Object that itself stores Values. Without indirection, std::variant
71 // probes traits like is_*_constructible<Object> at instantiation time,
72 // which would require Object complete here.
74 using variant_type = std::variant<std::nullptr_t,
75 bool,
76 std::int64_t,
77 std::uint64_t,
78 float,
79 double,
80 std::string,
81 Array,
82 std::unique_ptr<Object>>;
83
84 // Every member that touches v_ is DECLARED here and DEFINED out-of-line
85 // in object.hpp. Reason: instantiating variant<..., unique_ptr<Object>>
86 // operations (including the constructor's exception-handling rollback)
87 // requires unique_ptr<Object>::~unique_ptr to be instantiable, which
88 // requires Object complete — which isn't the case until object.hpp.
89
91 Value() noexcept;
93 Value(Value&& other) noexcept;
95 Value& operator=(Value&& other) noexcept;
97 Value(const Value& other);
99 Value& operator=(const Value& other);
102
103 // Braced-list assignment for ergonomic in-place writes:
104 // m["tags"] = {"a", "b"}; // → Array
105 // m["sub"] = {{"k", 1}, {"k2", 2}}; // → Object (pair-like elements)
106 // The two overloads disambiguate by element type: bare values pick the
107 // Array overload; brace-pair elements pick the Object overload (Value
108 // has no 2-arg constructor, so they only match the pair form).
109 // An empty list `= {}` is ambiguous — use `md::Array{}` or `md::Object{}`
110 // explicitly when you mean empty.
112 Value& operator=(std::initializer_list<Value> il);
114 Value& operator=(std::initializer_list<std::pair<const std::string, Value>> il);
115
117 Value(std::nullptr_t) noexcept;
118
120 template <class B>
121 requires std::same_as<B, bool>
122 Value(B b) noexcept;
123
125 template <detail::SignedIntLike T>
126 Value(T x) noexcept;
127
129 template <detail::UnsignedIntLike T>
130 Value(T x) noexcept;
131
132 // FloatLike covers float/double/long double; the constructor stores
133 // single-precision input as `float` and everything else as `double`.
135 template <detail::FloatLike T>
136 Value(T x) noexcept;
137
139 Value(std::string s);
141 Value(std::string_view s);
143 Value(const char* s);
144
146 Value(Array a);
148 Value(Object o);
149
150 // Build an Object directly from a pair-shaped braced list. Lets nested
151 // objects parse naturally:
152 // m["sub"] = {{"a", {{"b", 1}}}}; // outer Object, inner Object
153 // We deliberately do NOT add `Value(initializer_list<Value>)` for arrays
154 // because that would change the meaning of `Value{42}` from scalar to
155 // single-element Array.
157 Value(std::initializer_list<std::pair<const std::string, Value>> il);
158
160 template <class T>
161 Value(T*) = delete;
162
163 // --- predicates --------------------------------------------------
164
166 [[nodiscard]] bool is_null() const noexcept;
168 [[nodiscard]] bool is_bool() const noexcept;
170 [[nodiscard]] bool is_int() const noexcept;
172 [[nodiscard]] bool is_uint() const noexcept;
174 [[nodiscard]] bool is_float() const noexcept;
176 [[nodiscard]] bool is_double() const noexcept;
178 [[nodiscard]] bool is_number() const noexcept;
180 [[nodiscard]] bool is_string() const noexcept;
182 [[nodiscard]] bool is_array() const noexcept;
184 [[nodiscard]] bool is_object() const noexcept;
185
186 // --- strict accessors -------------------------------------------
187
189 [[nodiscard]] bool as_bool() const;
191 [[nodiscard]] std::int64_t as_int() const;
193 [[nodiscard]] std::uint64_t as_uint() const;
195 [[nodiscard]] float as_float() const;
197 [[nodiscard]] double as_double() const;
198
200 [[nodiscard]] std::string& as_string();
202 [[nodiscard]] const std::string& as_string() const;
204 [[nodiscard]] Array& as_array();
206 [[nodiscard]] const Array& as_array() const;
208 [[nodiscard]] Object& as_object();
210 [[nodiscard]] const Object& as_object() const;
211
212 // --- pointer accessors (noexcept) -------------------------------
213
215 [[nodiscard]] bool* as_bool_if() noexcept;
217 [[nodiscard]] const bool* as_bool_if() const noexcept;
219 [[nodiscard]] std::int64_t* as_int_if() noexcept;
221 [[nodiscard]] const std::int64_t* as_int_if() const noexcept;
223 [[nodiscard]] std::uint64_t* as_uint_if() noexcept;
225 [[nodiscard]] const std::uint64_t* as_uint_if() const noexcept;
227 [[nodiscard]] float* as_float_if() noexcept;
229 [[nodiscard]] const float* as_float_if() const noexcept;
231 [[nodiscard]] double* as_double_if() noexcept;
233 [[nodiscard]] const double* as_double_if() const noexcept;
235 [[nodiscard]] std::string* as_string_if() noexcept;
237 [[nodiscard]] const std::string* as_string_if() const noexcept;
239 [[nodiscard]] Array* as_array_if() noexcept;
241 [[nodiscard]] const Array* as_array_if() const noexcept;
243 [[nodiscard]] Object* as_object_if() noexcept;
245 [[nodiscard]] const Object* as_object_if() const noexcept;
246
248 template <class T>
249 [[nodiscard]] T* get_if() noexcept;
251 template <class T>
252 [[nodiscard]] const T* get_if() const noexcept;
253
255 template <class T>
256 [[nodiscard]] T value_or(T fallback) const;
257
259 [[nodiscard]] variant_type& raw() noexcept;
261 [[nodiscard]] const variant_type& raw() const noexcept;
262
264 [[nodiscard]] std::size_t index() const noexcept;
265
267 friend bool operator==(const Value& a, const Value& b) noexcept;
269 friend bool operator!=(const Value& a, const Value& b) noexcept;
270
271private:
272 variant_type v_;
273};
274
275// --- factory helper declarations ----------------------------------------
276
278[[nodiscard]] Value null() noexcept;
280[[nodiscard]] Value boolean(bool b) noexcept;
281
283template <class T>
284 requires(detail::SignedIntLike<T> || detail::UnsignedIntLike<T> || detail::FloatLike<T>)
285[[nodiscard]] inline Value number(T v) noexcept {
286 return Value{v};
287}
288
290[[nodiscard]] Value string(std::string s);
292[[nodiscard]] Value string(std::string_view s);
294[[nodiscard]] Value string(const char* s);
295
297[[nodiscard]] inline Array array() {
298 return Array{};
299}
301[[nodiscard]] inline Array array(const std::initializer_list<Value> il) {
302 return Array(il);
303}
304
305// object() / object({...}) factories are defined in object.hpp.
306
307} // namespace md
Ordered-by-insertion-time-ish string-keyed map of Values, with transparent string-view lookup and JSO...
Definition object.hpp:21
Discriminated union holding one of the JSON-like alternatives (null, bool, signed/unsigned integer,...
Definition value.hpp:67
std::string * as_string_if() noexcept
Return a pointer to the string, or nullptr if not held.
Definition object.hpp:539
bool is_int() const noexcept
True if the value holds a signed integer (std::int64_t).
Definition object.hpp:433
bool is_bool() const noexcept
True if the value holds a bool.
Definition object.hpp:430
bool is_uint() const noexcept
True if the value holds an unsigned integer (std::uint64_t).
Definition object.hpp:436
bool is_object() const noexcept
True if the value holds a nested Object.
Definition object.hpp:454
std::string & as_string()
Access the string alternative; throws on type mismatch.
Definition object.hpp:488
bool is_null() const noexcept
True if the value holds nullptr.
Definition object.hpp:427
std::int64_t * as_int_if() noexcept
Return a pointer to the signed integer, or nullptr if not held.
Definition object.hpp:514
double as_double() const
Return as double, widening from int64/uint64/float as needed.
Definition object.hpp:472
Value() noexcept
Construct a null-valued Value.
Definition object.hpp:351
bool is_double() const noexcept
True if the value holds a double.
Definition object.hpp:442
std::int64_t as_int() const
Return the signed integer; throws on type mismatch.
Definition object.hpp:461
std::size_t index() const noexcept
Return the zero-based index of the active alternative.
Definition object.hpp:611
float as_float() const
Return the float; strict — throws if the value isn't a float.
Definition object.hpp:468
bool is_float() const noexcept
True if the value holds a float.
Definition object.hpp:439
T value_or(T fallback) const
Return the held T by value, or fallback if a different alternative is active.
Definition object.hpp:597
bool is_array() const noexcept
True if the value holds an Array.
Definition object.hpp:451
std::uint64_t as_uint() const
Return the unsigned integer; throws on type mismatch.
Definition object.hpp:464
Object * as_object_if() noexcept
Return a pointer to the nested object, or nullptr if not held.
Definition object.hpp:552
std::variant< std::nullptr_t, bool, std::int64_t, std::uint64_t, float, double, std::string, Array, std::unique_ptr< Object > > variant_type
Underlying std::variant type that stores the active alternative.
Definition value.hpp:82
T * get_if() noexcept
Return a pointer to the alternative of type T, or nullptr if not held.
Definition object.hpp:588
Array & as_array()
Access the array alternative; throws on type mismatch.
Definition object.hpp:494
std::uint64_t * as_uint_if() noexcept
Return a pointer to the unsigned integer, or nullptr if not held.
Definition object.hpp:520
variant_type & raw() noexcept
Return the underlying std::variant for advanced access.
Definition object.hpp:604
bool is_number() const noexcept
True if the value holds any numeric alternative.
Definition object.hpp:445
bool is_string() const noexcept
True if the value holds a std::string.
Definition object.hpp:448
bool as_bool() const
Return the bool; throws std::bad_variant_access on type mismatch.
Definition object.hpp:458
Array * as_array_if() noexcept
Return a pointer to the array, or nullptr if not held.
Definition object.hpp:545
bool * as_bool_if() noexcept
Return a pointer to the bool, or nullptr if the value isn't a bool.
Definition object.hpp:508
double * as_double_if() noexcept
Return a pointer to the double, or nullptr if not held.
Definition object.hpp:532
Object & as_object()
Access the nested object; throws on type mismatch.
Definition object.hpp:501
float * as_float_if() noexcept
Return a pointer to the float, or nullptr if not held.
Definition object.hpp:526