Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Stroika::Foundation::DataExchange::VariantValue Class Reference

Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in any weakly typed language (like JavaScript, Lisp, etc) More...

#include <VariantValue.h>

Classes

struct  EqualsComparer
 Compares values as if first normalized with Normalize () method. More...
 
struct  ThreeWayComparer
 Compares values as if first normalized with Normalize () method. More...
 

Public Types

enum class  Type : uint8_t
 Enumeration of variant types. More...
 

Public Member Functions

 VariantValue ()=default
 construct a VariantValue from most any 'basic type' you would expect to find in a weakly typed language (e.g. lisp, javascript)
 
nonvirtual VariantValueoperator= (VariantValue &&rhs) noexcept=default
 
nonvirtual bool empty () const
 
nonvirtual String ToString () const
 
template<typename RETURNTYPE >
requires (Private_::IVariantValueAsBasic_<RETURNTYPE> or (Common::IOptional<RETURNTYPE> and Private_::IVariantValueAsBasic_<Common::ExtractValueType_t<RETURNTYPE>>))
nonvirtual RETURNTYPE As () const
 
nonvirtual operator bool () const
 return true iff value GetType () != null;
 
nonvirtual bool IsConvertibleTo (Type to) const
 
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 to')
 
nonvirtual VariantValue Normalize () const
 
nonvirtual strong_ordering operator<=> (const VariantValue &rhs) const
 compares as if first normalized with Normalize()
 
nonvirtual bool operator== (const VariantValue &rhs) const
 compares as if first normalized with Normalize()
 

Detailed Description

Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in any weakly typed language (like JavaScript, Lisp, etc)

These objects are internally efficiently copied (shared_ptr), but have copy by value semantics (since they are never modifiable).

Design Notes: o This is similar to std::variant, but generally simpler to use because of brevity, and a well chosen (see COM, or JSON) but useful predefined set of things that are shared in the variant.

o Note that it is never possible to create circular references (e.g. with Array or Map) types because these are constructed from existing already constructed VariantValue objects, and can never be modified thereafter.

o Note that this VariantValue is analogous to, and inspired by, the Microsoft COM VARIANT object type.

o The reason we chose to interpret As<T>() as generating an exception, instead of an assertion, was based on experience using the VariantValue class. We found it was typically used extracting data from an unreliable external source (typically JSON - either from a web service or configuration data), and if that was mal-formed (since there is no JSON schema) - we would assert. At least for this usage (and that now seems the primary one) exceptions on type mismatches seemed most helpful.

From section from section 3.9.1 of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf There are five standard signed integer types : signed char, short int, int, long int, and long long int. In this list, each type provides at least as much storage as those preceding it in the list. For each of the standard signed integer types, there exists a corresponding (but different) standard unsigned integer type: unsigned char, unsigned short int, unsigned int, unsigned long int, and unsigned long long int, each of which occupies the same amount of storage and has the same alignment requirements.

So for now - we just store the largest signed and unsigned integer types and cast down to what the user users/requests.

Note
Comparisons: o static_assert (totally_ordered<VariantValue>); // but notice that such comparisons expensive, as they imply normalizing
       EQUALSCOMPARER:

       If exactTypeMatchOnly is true, no type coercion takes place, but by default types
       are automatically coerced, if reasonable, so that they can be compared for equality.

       When comparing two items, at least one of which is a floating point number, the other type
       is coerced into a floating point number and  @Math::NearlyEquals() is used
       (because often these values come from serialization/deserialization which loses a tiny bit of precision).
       Note that NANs compare as equal, and Equals ("NAN", Math::nan<double> ()) compares as true.

       When comparing any other types (except Map or Array) with a String, the to types are coerced
       into Strings, and compared as strings.
Note
- VariantValue can be used to 'normalize' various type representations for externalization in String formats. Not perfect for all types (like string) - but does about as well as you can reasonably... And often good enuf.

For example, dates written out as ISO-2022 strings, and read back as such. Numbers - like NAN written portably and read back. For structured types (like arrays) - but explicitly use JSON reader/writer ;-).

Example Usage
optional<String> x1;
optional<Date> x2;
String representAs1 = VariantValue{x1}.As<String> ();
String representAs2 = VariantValue{x12}.As<String> ();
x1 = VariantValue{representAs1}.As<optional<String>> ();
x2= VariantValue{representAs2}.As<optional<String>> ();
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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...
Example Usage
auto roundTrip = [] (auto tValue) {
using T = remove_cvref_t<decltype (tValue)>;
String representation = VariantValue{tValue}.As<String> ();
return VariantValue{representation}.As<T> ();
};
EXPECT_EQ (roundTrip ("v"_k), "v");
EXPECT_EQ (roundTrip (5), 5);
EXPECT_EQ (roundTrip (optional<int>{}), optional<int>{});
EXPECT_EQ (roundTrip (optional<Date>{}), optional<Date>{});
constexpr DateTime kT1_ = DateTime{Date{January / 3 / 1944}};
EXPECT_EQ (roundTrip (kT1_), kT1_);
// But doesn't work perfectly. Empty string and optional<String>{} get represented as the same so that's ambiguous
EXPECT_EQ (roundTrip (String{}), String{});
EXPECT_EQ (roundTrip (optional<String>{}), nullopt);
EXPECT_EQ (roundTrip (optional<String>{String{}}), nullopt); // oops - but really how could it tell?
Note
Satisfies Concepts: o static_assert (regular<VariantValue);

TODO:

@todo   XPath / JPath / JSONPath feature in DataExchange::VariantValue - https://github.com/SophistSolutions/Stroika/issues/110

@todo   POSSIBLY add support for Precision (see Characters::Float2String) - once that module has cleaned up
        notion of precision. Not sure how to add unobtrusively. - for As<String>()? optional param?...
        Maybe Float2StringOptions is optional param to As<String> ()???

@todo   Add SINFAE CTOR template, so we can lose explicit map<> CTOR, and handle other
        cases automatically, like vector<wstring> CTOR. And/or fix KeyValuePair<> ctor so
        maps 'convertible' key and convertible 'value' types.

@todo   Use Debug::AssertExternallySynchronizedMutex<> to assure not used from multiple threads.

@todo   Re-review the signed/unsigned compare etc code. I think its all correct, but its tricky enough to
        warrant a careful review

Definition at line 170 of file Foundation/DataExchange/VariantValue.h.

Member Enumeration Documentation

◆ Type

Enumeration of variant types.

Note
Design Note It is important that ALL these types can easily and naturally be represented in JSON, but that the types here are a strict superset of the types that are native to JSON, so that roundtripping through JSON will not always produce the exact same object. For example, if you have BLOB, and write it in JSON, it might be represented as a string (or array of bytes). When you map back, its type will be that type its represented as. But - however its represented, if you say T x; VariantValue v = x; VariantValue v2 = fromJSON (toJSON (v)); v2 == v1; // maybe false T y = v2.As<T> (); // will produce value x == y
Common::DefaultNames<> supported
the Normalize () method can be used to return the limited subset of information that appears in JSON (but beware, that also sorts the mappings).

Definition at line 210 of file Foundation/DataExchange/VariantValue.h.

Constructor & Destructor Documentation

◆ VariantValue()

Stroika::Foundation::DataExchange::VariantValue::VariantValue ( )
default

construct a VariantValue from most any 'basic type' you would expect to find in a weakly typed language (e.g. lisp, javascript)

When constructing a 'stringish' VariantValue, any arguments used to construct a String object maybe used, with the same constraints (e.g. req basic_string_view<char> MUST contain only ascii text)

Note
Most constructors are not explicit to make it more easy to assign to (could do operator= for that)? A few are marked as explicit, only because its too easy to accidentally invoke (as with http://stroika-bugs.sophists.com/browse/STK-739) - Iterable<VariantValue> or Set<VariantValue> etc... Roughly, explicit types can be used directly, and composite (aggregating) types are explicit.

Member Function Documentation

◆ operator=()

nonvirtual VariantValue & Stroika::Foundation::DataExchange::VariantValue::operator= ( VariantValue &&  rhs)
defaultnoexcept

Assign anything to a VariantValue you can construct a VariantValue with.

◆ empty()

bool VariantValue::empty ( ) const

Return if the given object is logically 'empty'. The meaning of this depends on the type of the variant. For example, eNull type is always empty. Most other types are empty iff their native type (e.g. basic_string) is 'empty'. A FLOAT is empty iff its std::isnan(). Booleans and integer types are never empty (even if zero/false). Maps and arrays are empty iff they contain no elements;

Definition at line 276 of file Foundation/DataExchange/VariantValue.cpp.

◆ ToString()

String VariantValue::ToString ( ) const
See also
Characters::ToString() Return a debug-friendly, display version of the current variant. This is not guaranteed parsable or usable except for debugging.

Definition at line 323 of file Foundation/DataExchange/VariantValue.cpp.

◆ As()

template<typename RETURNTYPE >
requires (Private_::IVariantValueAsBasic_<RETURNTYPE> or (Common::IOptional<RETURNTYPE> and Private_::IVariantValueAsBasic_<Common::ExtractValueType_t<RETURNTYPE>>))
nonvirtual RETURNTYPE Stroika::Foundation::DataExchange::VariantValue::As ( ) const

Only (see requires) types supported; There is no generic As<T> implementation.

If the caller attempts a conversion that isn't supported, or doesn't make sense then DataExchange::BadFormatException will be thrown (not assertion error).

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>).

Note
Why As<T> () instead of conversion operator / static_cast support? 1) - not sure - maybe a mistake 2) without explicit, can cause confusion with construction conversions etc. With explicit, maybe still causes confusion with unform initialization. 3) At least for the case of bool - operator bool and As<bool> can return different results (maybe also a mistake?).
About As<String> () and As<wstring> () Conversion to these types is ALWAYS supported, no matter what the source type. Coercion of Date/DateTime values uses ISO8601 format. Coercion of array and map values is vaguely json-ish in format, readable, but not intended to be regular enough to be parsable.
Unsigned int and Signed int both converted to string representations base 10 (might have considered hex for base 16, but no).
About As<bool> () Coerces String value 'true' - case sensitive - to true, and any integer or unsigned integer value to true if non-zero.
About As<BLOB> () If type=eBLOB, return that. If type = null, return empty blob. Else, converts any type to String, and use base64 conversion. Similarly - for As<String> on types that are eBLOB - they are decoded as Base64.
About As<Date> (), About As<DateTime> () Null maps to empty Date.DateTime; String value mapped to Date/DateTIme assuming ISO8601 string format - may return empty or exception on failure.
About As<float> (), As<double> (), As<long double> () This converts strings and integer and floating point types. 'empty' - or the null type - is converted to nan(). Similarly, if the string cannot be converted, a nan will be returned. Other types (like Mapping) generate an exception.

◆ operator bool()

Stroika::Foundation::DataExchange::VariantValue::operator bool ( ) const
explicit

return true iff value GetType () != null;

Note
- this is NOT the same as not empty ()!
this is NOT the same same as As<bool> ()

And because of that - at least partly - we avoided support explicit operator (for each type in the basic variant like explicit operator String).

See also
As<T>

Definition at line 58 of file Foundation/DataExchange/VariantValue.inl.

◆ IsConvertibleTo()

bool VariantValue::IsConvertibleTo ( Type  to) const

Return true if this VariantValue would be convertible to the argument type. Note - not just potentially convertible, but the data is actually formatted to allow the conversion. So for example, if to is DateTime, then an ill formed string would NOT be IsConvertibleTo

See also
ConvertTo

Definition at line 358 of file Foundation/DataExchange/VariantValue.cpp.

◆ ConvertTo()

VariantValue VariantValue::ConvertTo ( Type  to) const

Return this VariantValue converted to the given type (as if by As<T> for the T appropriate to 'Type to')

This will throw DataExchange::BadFormatException if the conversion doesn't make sense.

Definition at line 373 of file Foundation/DataExchange/VariantValue.cpp.

◆ Normalize()

VariantValue VariantValue::Normalize ( ) const

Return a (possibly new, possibly same) object with certain 'features' standardized. Essentially this converts to basic JSON-writable types. So BLOB, and Date, etc, converted to string, integers converted to Float (number), nans converted to strings, etc.

This also produced 'sorted' mappings.

You generally don't need to use this, but its helpful for the definition of equality and comparison.

Definition at line 408 of file Foundation/DataExchange/VariantValue.cpp.


The documentation for this class was generated from the following files: