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