Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Foundation/DataExchange/VariantValue.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Memory_VariantValue_h_
5#define _Stroika_Foundation_Memory_VariantValue_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <compare>
10#include <concepts>
11#include <map>
12#include <vector>
13
14#if qStroika_HasComponent_boost
15DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wstringop-overflow\""); // for g++-13 g++-release-sanitize_address_undefined warning: 'long unsigned int __atomic_sub_fetch_8(volatile void*, long unsigned int, int)' writing 8 bytes into a region of size 0 overflows the destination [-Wstringop-overflow=
16#include <boost/json/value.hpp>
17DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wstringop-overflow\"");
18#endif
19
21#include "Stroika/Foundation/Common/Common.h"
23#include "Stroika/Foundation/Containers/Mapping.h"
24#include "Stroika/Foundation/Containers/Sequence.h"
27
28/**
29 * \file
30 *
31 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
32 *
33 */
34
36
37 using Characters::String;
38 using Containers::Mapping;
39 using Containers::Sequence;
40 using Memory::BLOB;
41 using Time::Date;
42 using Time::DateTime;
43
44 class VariantValue;
45 namespace Private_ {
46 template <typename T>
47 concept IVariantValueAsBasic_ =
48 Common::IAnyOf<T, bool, BLOB, Date, DateTime, wstring, String, Mapping<String, VariantValue>, map<wstring, VariantValue>, Sequence<VariantValue>, vector<VariantValue>>
49#if qStroika_HasComponent_boost
50 or same_as<T, boost::json::value>
51#endif
52 or integral<T> or floating_point<T>;
53 }
54
55 /**
56 * \brief Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in any weakly typed language (like JavaScript, Lisp, etc)
57 *
58 * These objects are internally efficiently copied (shared_ptr), but have copy
59 * by value semantics (since they are never modifiable).
60 *
61 * Design Notes:
62 * o This is similar to std::variant, but generally simpler to use because of brevity,
63 * and a well chosen (see COM, or JSON) but useful predefined set of things that are shared in the
64 * variant.
65 *
66 * o Note that it is never possible to create circular references (e.g. with Array or Map)
67 * types because these are constructed from existing already constructed VariantValue
68 * objects, and can never be modified thereafter.
69 *
70 * o Note that this VariantValue is analogous to, and inspired by, the Microsoft
71 * COM VARIANT object type.
72 *
73 * o The reason we chose to interpret As<T>() as generating an exception, instead of an
74 * assertion, was based on experience using the VariantValue class. We found it was
75 * typically used extracting data from an unreliable external source (typically JSON -
76 * either from a web service or configuration data), and if that was mal-formed
77 * (since there is no JSON schema) - we would assert. At least for this usage (and
78 * that now seems the primary one) exceptions on type mismatches seemed most helpful.
79 *
80 * From section from section 3.9.1 of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
81 * There are five standard signed integer types : signed char, short int, int,
82 * long int, and long long int. In this list, each type provides at least as much
83 * storage as those preceding it in the list.
84 * For each of the standard signed integer types, there exists a corresponding (but different)
85 * standard unsigned integer type: unsigned char, unsigned short int, unsigned int, unsigned long int,
86 * and unsigned long long int, each of which occupies the same amount of storage and has the
87 * same alignment requirements.
88 *
89 * So for now - we just store the largest signed and unsigned integer types and cast down to what
90 * the user users/requests.
91 *
92 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
93 * o static_assert (totally_ordered<VariantValue>); // but notice that such comparisons expensive, as they imply normalizing
94 *
95 * @todo UPDATE DOCS FOR EQUALS COMPARER AND SHARE APPRORIATE DOCS FOR THREEWAY COMPARER AND SAY WHAT PART IS FOR
96 ANY COMPARER...
97 *
98 * EQUALSCOMPARER:
99 *
100 * If exactTypeMatchOnly is true, no type coercion takes place, but by default types
101 * are automatically coerced, if reasonable, so that they can be compared for equality.
102 *
103 * When comparing two items, at least one of which is a floating point number, the other type
104 * is coerced into a floating point number and @Math::NearlyEquals() is used
105 * (because often these values come from serialization/deserialization which loses a tiny bit of precision).
106 * Note that NANs compare as equal, and Equals ("NAN", Math::nan<double> ()) compares as true.
107 *
108 * When comparing any other types (except Map or Array) with a String, the to types are coerced
109 * into Strings, and compared as strings.
110 *
111 *
112 * \note - VariantValue can be used to 'normalize' various type representations for externalization in String formats.
113 * Not perfect for all types (like string) - but does about as well as you can reasonably... And often good enuf.
114 *
115 * For example, dates written out as ISO-2022 strings, and read back as such. Numbers - like NAN written portably and read back.
116 * For structured types (like arrays) - but explicitly use JSON reader/writer ;-).
117 *
118 * \par Example Usage
119 * \code
120 * optional<String> x1;
121 * optional<Date> x2;
122 *
123 * String representAs1 = VariantValue{x1}.As<String> ();
124 * String representAs2 = VariantValue{x12}.As<String> ();
125 *
126 * x1 = VariantValue{representAs1}.As<optional<String>> ();
127 * x2= VariantValue{representAs2}.As<optional<String>> ();
128 * \endcode
129 *
130 * \par Example Usage
131 * \code
132 * auto roundTrip = [] (auto tValue) {
133 * using T = remove_cvref_t<decltype (tValue)>;
134 * String representation = VariantValue{tValue}.As<String> ();
135 * return VariantValue{representation}.As<T> ();
136 * };
137 * EXPECT_EQ (roundTrip ("v"_k), "v");
138 * EXPECT_EQ (roundTrip (5), 5);
139 * EXPECT_EQ (roundTrip (optional<int>{}), optional<int>{});
140 * EXPECT_EQ (roundTrip (optional<Date>{}), optional<Date>{});
141 * constexpr DateTime kT1_ = DateTime{Date{January / 3 / 1944}};
142 * EXPECT_EQ (roundTrip (kT1_), kT1_);
143 *
144 * // But doesn't work perfectly. Empty string and optional<String>{} get represented as the same so that's ambiguous
145 * EXPECT_EQ (roundTrip (String{}), String{});
146 * EXPECT_EQ (roundTrip (optional<String>{}), nullopt);
147 * EXPECT_EQ (roundTrip (optional<String>{String{}}), nullopt); // oops - but really how could it tell?
148 * \endcode
149 *
150 * \note Satisfies Concepts:
151 * o static_assert (regular<VariantValue);
152 *
153 * TODO:
154 *
155 * @todo XPath / JPath / JSONPath feature in DataExchange::VariantValue - https://github.com/SophistSolutions/Stroika/issues/110
156 *
157 * @todo POSSIBLY add support for Precision (see Characters::Float2String) - once that module has cleaned up
158 * notion of precision. Not sure how to add unobtrusively. - for As<String>()? optional param?...
159 * Maybe Float2StringOptions is optional param to As<String> ()???
160 *
161 * @todo Add SINFAE CTOR template, so we can lose explicit map<> CTOR, and handle other
162 * cases automatically, like vector<wstring> CTOR. And/or fix KeyValuePair<> ctor so
163 * maps 'convertible' key and convertible 'value' types.
164 *
165 * @todo Use Debug::AssertExternallySynchronizedMutex<> to assure not used from multiple threads.
166 *
167 * @todo Re-review the signed/unsigned compare etc code. I think its all correct, but its tricky enough to
168 * warrant a careful review
169 */
170 class [[nodiscard]] VariantValue {
171 private:
172 /**
173 * Internal format for storing floating point data in a VariantValue.
174 */
175 using FloatType_ = long double;
176
177 private:
178 /**
179 * Internal format for storing int data in a VariantValue.
180 */
181 using IntegerType_ = long long int;
182
183 private:
184 /**
185 * Internal format for storing unsigned int data in a VariantValue.
186 */
187 using UnsignedIntegerType_ = unsigned long long int;
188
189 public:
190 /**
191 * \brief Enumeration of variant types
192 *
193 * \note Design Note
194 * It is important that ALL these types can easily and naturally be represented in JSON, but that
195 * the types here are a strict superset of the types that are native to JSON, so that roundtripping
196 * through JSON will not always produce the exact same object. For example, if you have BLOB, and write
197 * it in JSON, it might be represented as a string (or array of bytes). When you map back, its type will be
198 * that type its represented as. But - however its represented, if you say
199 * T x;
200 * VariantValue v = x;
201 * VariantValue v2 = fromJSON (toJSON (v));
202 * v2 == v1; // maybe false
203 * T y = v2.As<T> (); // will produce value x == y
204 *
205 * \note Common::DefaultNames<> supported
206 *
207 * \note the Normalize () method can be used to return the limited subset of information that appears in JSON
208 * (but beware, that also sorts the mappings).
209 */
210 enum class Type : uint8_t {
211 eNull,
212 eBLOB,
213 eBoolean,
214 eInteger,
215 eUnsignedInteger,
216 eFloat,
217 eDate,
218 eDateTime,
219 eString,
220 eArray,
221 eMap, // Mapping<String,VariantValue>
222
223 Stroika_Define_Enum_Bounds (eNull, eMap)
224 };
225
226 public:
227 /**
228 */
229 using Type::eArray;
230 using Type::eBLOB;
231 using Type::eBoolean;
232 using Type::eDate;
233 using Type::eDateTime;
234 using Type::eFloat;
235 using Type::eInteger;
236 using Type::eMap;
237#if qCompilerAndStdLib_InternalCompilerErrorTSubCopy_Buggy
238 static constexpr Type eNull = Type::eNull;
239#else
240 using Type::eNull;
241#endif
242 using Type::eString;
243 using Type::eUnsignedInteger;
244
245 public:
246 /**
247 * \brief construct a VariantValue from most any 'basic type' you would expect to find in a weakly typed language (e.g. lisp, javascript)
248 *
249 * When constructing a 'stringish' VariantValue, any arguments used to construct a String object maybe used, with the same constraints
250 * (e.g. req basic_string_view<char> MUST contain only ascii text)
251 *
252 * \note Most constructors are not explicit to make it more easy to assign to (could do operator= for that)?
253 * A few are marked as explicit, only because its too easy to accidentally invoke
254 * (as with http://stroika-bugs.sophists.com/browse/STK-739) - Iterable<VariantValue> or Set<VariantValue> etc...
255 * Roughly, explicit types can be used directly, and composite (aggregating) types are explicit.
256 *
257 * \todo Can shorten this a bit using templates and concepts (integral, floating_point etc - like I did for "StringIsh").
258 */
259 VariantValue () = default;
260 VariantValue (nullopt_t);
261 VariantValue (nullptr_t);
262 VariantValue (bool val);
263 VariantValue (const BLOB& val);
264 VariantValue (signed char val);
265 VariantValue (short int val);
266 VariantValue (int val);
267 VariantValue (long int val);
268 VariantValue (long long int val);
269 VariantValue (unsigned char val);
270 VariantValue (unsigned short int val);
271 VariantValue (unsigned int val);
272 VariantValue (unsigned long int val);
273 VariantValue (unsigned long long int val);
274 VariantValue (float val);
275 VariantValue (double val);
276 VariantValue (long double val);
277 VariantValue (const Date& val);
278 VariantValue (const DateTime& val);
279 template <Characters::IConvertibleToString STRINGISH_T>
280 VariantValue (STRINGISH_T&& val)
281 requires (not same_as<remove_cvref_t<STRINGISH_T>, String>);
282 VariantValue (const String& val);
283 explicit VariantValue (const map<wstring, VariantValue>& val);
285 explicit VariantValue (const Mapping<String, VariantValue>& val);
286 explicit VariantValue (const vector<VariantValue>& val);
287 explicit VariantValue (Sequence<VariantValue>&& val);
288 explicit VariantValue (const Sequence<VariantValue>& val);
290 VariantValue (const VariantValue& src) = default;
291 VariantValue (VariantValue&& src) noexcept = default;
292 template <typename T>
293 VariantValue (const optional<T>& val)
294 requires (is_convertible_v<T, VariantValue>); // @todo redo with constraint/typename syntax (I think converitble_to<VariantValue>)
295#if qStroika_HasComponent_boost
296 VariantValue (const boost::json::value& val);
297#endif
298
299 public:
300 /**
301 * Assign anything to a VariantValue you can construct a VariantValue with.
302 */
303 nonvirtual VariantValue& operator= (VariantValue&& rhs) noexcept = default;
304 nonvirtual VariantValue& operator= (const VariantValue& rhs) = default;
305 template <typename T>
306 nonvirtual VariantValue& operator= (T&& val)
307 requires (requires (T x) { VariantValue{x}; }); // @todo redo with constraint/typename syntax (I think converitble_to<VariantValue>)
308
309 public:
310 /**
311 */
312 nonvirtual Type GetType () const;
313
314 public:
315 /**
316 * Return if the given object is logically 'empty'. The meaning of this depends
317 * on the type of the variant. For example, eNull type is always empty. Most
318 * other types are empty iff their native type (e.g. basic_string) is 'empty'.
319 * A FLOAT is empty iff its std::isnan().
320 * Booleans and integer types are never empty (even if zero/false).
321 * Maps and arrays are empty iff they contain no elements;
322 */
323 nonvirtual bool empty () const;
324
325 public:
326 /**
327 * @see Characters::ToString()
328 * Return a debug-friendly, display version of the current variant. This is not guaranteed parsable or usable except for debugging.
329 */
330 nonvirtual String ToString () const;
331
332 public:
333 /**
334 * Only (see requires) types supported; There is no generic As<T> implementation.
335 *
336 * If the caller attempts a conversion that isn't supported, or doesn't make sense
337 * then DataExchange::BadFormatException will be thrown (not assertion error).
338 *
339 * For the optional<OF_T> specialization, if this is empty (nullopt counts as empty) - then return nullopt, and only otherwise attempt to coerce (As<the optional type>).
340 *
341 * \note Why As<T> () instead of conversion operator / static_cast support?
342 * 1) - not sure - maybe a mistake
343 * 2) without explicit, can cause confusion with construction conversions etc. With explicit, maybe
344 * still causes confusion with unform initialization.
345 * 3) At least for the case of bool - operator bool and As<bool> can return different results (maybe also a mistake?).
346 *
347 * \note About As<String> () and As<wstring> ()
348 * Conversion to these types is ALWAYS supported, no matter what the source type.
349 * Coercion of Date/DateTime values uses ISO8601 format.
350 * Coercion of array and map values is vaguely json-ish in format, readable, but not intended
351 * to be regular enough to be parsable.
352 *
353 * \note Unsigned int and Signed int both converted to string representations base 10 (might have
354 * considered hex for base 16, but no).
355 *
356 * \note About As<bool> ()
357 * Coerces String value 'true' - case sensitive - to true, and any integer or unsigned integer value
358 * to true if non-zero.
359 *
360 * \note About As<BLOB> ()
361 * If type=eBLOB, return that. If type = null, return empty blob.
362 * Else, converts any type to String, and use base64 conversion.
363 * Similarly - for As<String> on types that are eBLOB - they are decoded as Base64.
364 *
365 * \note About As<Date> (), About As<DateTime> ()
366 * Null maps to empty Date.DateTime;
367 * String value mapped to Date/DateTIme assuming ISO8601 string format - may return empty or exception on failure.
368 *
369 * \note About As<float> (), As<double> (), As<long double> ()
370 * This converts strings and integer and floating point types. 'empty' - or the null type - is converted to nan().
371 * Similarly, if the string cannot be converted, a nan will be returned. Other types (like Mapping) generate
372 * an exception.
373 *
374 * \todo Try to find a neater way to express the IAnyOf OPTIONAL case - shouldn't need to repeat everything. Tried using
375 * template <typename OF_T> nonvirtual optional<OF_T> As () const requires (requires (OF_T v) { VariantValue{}.As<OF_T> (); });
376 * But somehow on visual studio that produced ambiguity errors from compiler.
377 */
378 template <typename RETURNTYPE>
379 nonvirtual RETURNTYPE As () const
380 requires (Private_::IVariantValueAsBasic_<RETURNTYPE> or
381 (Common::IOptional<RETURNTYPE> and Private_::IVariantValueAsBasic_<Common::ExtractValueType_t<RETURNTYPE>>));
382
383 public:
384 /**
385 * \brief return true iff value GetType () != null;
386 *
387 * \note - this is NOT the same as not empty ()!
388 * \note this is NOT the same same as As<bool> ()
389 *
390 * And because of that - at least partly - we avoided support explicit operator (for each type in the basic variant like explicit operator String).
391 * \see As<T>
392 */
393 nonvirtual explicit operator bool () const;
394
395 // Consider if explicit operator T a suitable replacement for As() pattern? Just more standard c++ syntax for the same idea? Experimenting in VariantValue
396 // maybe allow BOTH - cuz As<> sytnax often better/more functional a then b then c - not having to put the type conversion before thing its operating on...
397 //CONSIDER FIXING issue with diff for operator bool and As<bool> () - confusing special case...
398 template <typename T>
399 explicit operator T () const
400 requires (requires (T) { As<T> (); })
401 {
402 return As<T> ();
403 }
404
405 public:
406 /**
407 * Return true if this VariantValue would be convertible to the argument type.
408 * Note - not just potentially convertible, but the data is actually formatted to allow the conversion.
409 * So for example, if to is DateTime, then an ill formed string would NOT be IsConvertibleTo
410 *
411 * @see ConvertTo
412 */
413 nonvirtual bool IsConvertibleTo (Type to) const;
414
415 public:
416 /**
417 * \brief Return this VariantValue converted to the given type (as if by As<T> for the T appropriate to 'Type to')
418 *
419 * This will throw DataExchange::BadFormatException if the conversion doesn't make sense.
420 */
421 nonvirtual VariantValue ConvertTo (Type to) const;
422
423 public:
424 /**
425 * Return a (possibly new, possibly same) object with certain 'features' standardized. Essentially this converts to basic
426 * JSON-writable types. So BLOB, and Date, etc, converted to string, integers converted to Float (number), nans converted
427 * to strings, etc.
428 *
429 * This also produced 'sorted' mappings.
430 *
431 * You generally don't need to use this, but its helpful for the definition of equality and comparison.
432 */
433 nonvirtual VariantValue Normalize () const;
434
435 private:
436 nonvirtual bool AsBool_ () const;
437 nonvirtual Date AsDate_ () const;
438 nonvirtual DateTime AsDateTime_ () const;
439 nonvirtual BLOB AsBLOB_ () const;
440 nonvirtual IntegerType_ AsInteger_ () const;
441 nonvirtual UnsignedIntegerType_ AsUnsignedInteger_ () const;
442 nonvirtual FloatType_ AsFloatType_ () const;
443 nonvirtual String AsString_ () const;
444 nonvirtual Mapping<String, VariantValue> AsMapping_ () const;
445 nonvirtual Sequence<VariantValue> AsSequence_ () const;
446#if qStroika_HasComponent_boost
447 nonvirtual boost::json::value AsBoostJSONValue_ () const;
448#endif
449
450 public:
451 /**
452 * \brief compares as if first normalized with Normalize()
453 */
454 nonvirtual strong_ordering operator<=> (const VariantValue& rhs) const;
455
456 public:
457 /**
458 * \brief compares as if first normalized with Normalize()
459 */
460 nonvirtual bool operator== (const VariantValue& rhs) const;
461
462 public:
463 struct EqualsComparer;
464
465 public:
466 struct ThreeWayComparer;
467
468 private:
469 struct IRep_;
470
471 private:
472 shared_ptr<IRep_> fVal_;
473
474 private:
475 template <typename T>
476 struct TIRep_;
477
478 private:
479 static const shared_ptr<IRep_> kFalseRep_; // avoid even cheap needless allocations
480 static const shared_ptr<IRep_> kTrueRep_;
481 };
482 static_assert (totally_ordered<VariantValue>);
483
484 /**
485 * \brief Compares values as if first normalized with Normalize () method
486 *
487 * \note Before Stroika v3.0d1, EqualsComparer had an fExactTypeMatchOnly option, which defaulted false.
488 * This did various not clearly specified type coercions - being expensive, and buggy, and confusing.
489 *
490 * The trickiest part is that to do this properly, we needed to pass along the flag (through default constructors
491 * using thread_local storage trick) - and that was never done.
492 *
493 * I know of no use-case for this functionality, its ambiguous, and costly. So we lose it.
494 *
495 * The ONLY remaining coercions that are done in comparing, are that if two numbers (int,unsigned, float) are compared
496 * they are first promoted.
497 */
498 struct VariantValue::EqualsComparer : Common::ComparisonRelationDeclarationBase<Common::ComparisonRelationType::eEquals> {
499 constexpr EqualsComparer () = default;
500 nonvirtual bool operator() (const VariantValue& lhs, const VariantValue& rhs) const;
501 };
502
503 /**
504 * \brief Compares values as if first normalized with Normalize () method
505 *
506 * \note Before Stroika v3.0d1, EqualsComparer had an fExactTypeMatchOnly option, which defaulted false.
507 * This did various not clearly specified type coercions - being expensive, and buggy, and confusing.
508 *
509 * The trickiest part is that to do this properly, we needed to pass along the flag (through default constructors
510 * using thread_local storage trick) - and that was never done.
511 *
512 * I know of no use-case for this functionality, its ambiguous, and costly. So we lose it.
513 *
514 * The ONLY remaining coercions that are done in comparing, are that if two numbers (int,unsigned, float) are compared
515 * they are first promoted.
516 */
517 struct VariantValue::ThreeWayComparer : Common::ComparisonRelationDeclarationBase<Common::ComparisonRelationType::eThreeWayCompare> {
518 constexpr ThreeWayComparer () = default;
519 nonvirtual strong_ordering operator() (const VariantValue& lhs, const VariantValue& rhs) const;
520 };
521
522}
523
524/*
525 ********************************************************************************
526 ***************************** Implementation Details ***************************
527 ********************************************************************************
528 */
529#include "VariantValue.inl"
530
531#endif /*_Stroika_Foundation_Memory_VariantValue_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
VariantValue()=default
construct a VariantValue from most any 'basic type' you would expect to find in a weakly typed langua...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
Compares values as if first normalized with Normalize () method.
Compares values as if first normalized with Normalize () method.