AnyValue ======== The ``AnyValue`` class represents runtime introspectable values that can be empty, scalar, structured or array values. Objects of this type respect value semantics and can easily be passed, assigned or returned by value. .. contents:: :local: Architecture ------------ To support value semantics, the design is based on a single concrete ``AnyValue`` class that contains polymorphic implementations for the different values supported (scalar, array, etc.) The following figure shows the relevant part of the class diagram. .. image:: /images/AnyValue_classes.png Although largely an implementation detail, the underlying ``IValueData`` of an ``AnyValue`` object is never shared between different objects. This prevents nasty surprises when using these objects in a multithreaded environment. Construction ------------ Typically, ``AnyValue`` objects are created in one of two ways: * Statically: they are encoded directly by the programmer. * Dynamically: the values are runtime generated by parsing some other data structure. To accomodate for these different use cases, the ``AnyValue`` class provides different options to create a specific value object. For all ``AnyType`` objects, a corresponding ``AnyValue`` can be created with default values for its leaf values by using the following constructor: .. function:: explicit AnyValue::AnyValue(const AnyType& anytype) :param anytype: Type of the constructed ``AnyValue``. Construct a value object with the given type. There also exists a constructor that takes both an ``AnyType`` and an ``AnyValue`` parameter, that is provided mostly as a conversion constructor (see :ref:`conversion-methods`): .. function:: AnyValue::AnyValue(const AnyType& anytype, const AnyValue& anyvalue) :param anytype: Type of the constructed ``AnyValue``. :param anyvalue: Value for the constructed ``AnyValue``, with possible conversions. :throws InvalidConversionException: When the value couldn't be converted to the given type. Construct a value object with the given type and value. Empty value ^^^^^^^^^^^ The empty value is provided as a default null value, containing no data. Empty values are created by using the default ``AnyValue`` constructor:: // Use default constructor to create an empty value: AnyValue empty_value{}; Scalar values ^^^^^^^^^^^^^ Scalar values provide the elementary building blocks of all ``AnyValue`` objects. They contain a single arithmetic number (including booleans) or a string. The contained fundamental value type is an ``std::string`` or one of the following type aliases: .. type:: boolean .. type:: char8 .. type:: int8 .. type:: uint8 .. type:: int16 .. type:: uint16 .. type:: int32 .. type:: uint32 .. type:: int64 .. type:: uint64 .. type:: float32 .. type:: float64 The scalar ``AnyValue`` objects can be constructed from elementary integral, floating point or string types by using the converting constructors: .. function:: AnyValue::AnyValue(boolean val) const .. function:: AnyValue::AnyValue(char8 val) const .. function:: AnyValue::AnyValue(int8 val) const .. function:: AnyValue::AnyValue(uint8 val) const .. function:: AnyValue::AnyValue(int16 val) const .. function:: AnyValue::AnyValue(uint16 val) const .. function:: AnyValue::AnyValue(int32 val) const .. function:: AnyValue::AnyValue(uint32 val) const .. function:: AnyValue::AnyValue(uint64 val) const .. function:: AnyValue::AnyValue(float32 val) const .. function:: AnyValue::AnyValue(float64 val) const Create an ``AnyValue`` object with the passed value as underlying fundamental value. .. function:: AnyValue::AnyValue(const std::string& val) const .. function:: AnyValue::AnyValue(const char* val) const Create an ``AnyValue`` object of String type and initialize it with the given value. Due to these non-explicit constructors, it is possible to construct scalar ``AnyValue`` objects of specific numeric types by numerical conversion from standard numeric literal values (see also :ref:`conversion-methods` for the supported conversions):: // Create a 64 bit unsigned integer value from a standard integer literal: AnyValue my_uint64{UnsignedInteger64Type, 56}; Array values ^^^^^^^^^^^^ Array values represent fixed size arrays of values of the same type. These are constructed using a dedicated constructor:: // Create array value containing 20 boolean values and provide a name: AnyValue my_bool_array(20, BooleanType, "TwentyBooleans"); The last argument of this constructor is optional and if not provided, the typename will be an empty string. To construct an array value with specific element values in a single statement, a convenience function can be used that accepts a list of ``AnyValue`` elements:: AnyValue my_int_array = ArrayValue( {10, 20, 30}, "ThreeIntegers"); Structured values ^^^^^^^^^^^^^^^^^ Structured values correspond to the structured types (see :ref:`structured-types`). They are very similar to those type structures, but contain specific values in each of the leaf nodes, which are always scalar. As for the structured types, structured values can be constructed by adding subvalues to an existing structured value using the :func:`AnyValue::AddMember` method. The following example shows how this method can be used to populate a structured value:: // Create simple structured value containing: // - an account name of StringType // - an account number of UnsignedInteger64Type // - an activated flag of BooleanType auto account_val = EmptyStruct("AccountType"); account_val.AddMember("AccountName", {StringType, "John Vegas"}); account_val.AddMember("AccountNumber", {UnsignedInteger64Type, 44443789}); account_val.AddMember("Activated", true); As can be seen in the previous example, the value argument for the ``AddMember`` method can omit the preferred type if the automatically deduced type is correct (``Boolean`` in the last call). Again, one can create structures of structures, structures of arrays, arrays of structures, etc. To facilitate the static creation of structured values, a dedicated constructor is provided that accepts a braced-init-list of pairs of member names and values. This constructor also allows to provide a name for the type, which then needs to be passed as a final optional argument to the constructor:: // Create a customer value that contains: // - a name (StringType) // - an address structure, containing: // - a street field (StringType) // - a number field (UnsignedInteger16Type) // - a city field (StringType) // And provide a type name: "CustomerType". AnyValue customer_val({ {"name", {StringType, "John Vegas"}}, {"address", { {"street", {StringType, "Prosperity Road"}}, {"number", {UnsignedInteger16Type, 1255}}, {"city", "San Francisco"} }} }, "CustomerType"); Copy and move ------------- The ``AnyValue`` class provides copy and move constructors and assignment operators that are only slightly stricter than their ``AnyType`` counterparts. In general, all assignments are allowed, except when they would result in different elements of an array having different types. This means that assignment to array elements, or descendents thereof, will result in three alternatives: 1. Source and destination value have the exact same type: the destination will become a copy of the source. 2. The source value can be correctly converted to the destination type: the destination value will be a conversion of the source. 3. Failure to assignment will throw an exception of type ``InvalidConversionException``. For the scalar types, conversion requires that the underlying value can be converted to the destination type and that it fits into that representation (e.g. a negative integer cannot be assigned to an unsigned value type). For array values, conversion requires equal length arrays and compatibility for each of their elements. Structured values can be converted to one another if they have the same member names (in the exact same order) and their member values are compatible. Note that for both array and structured value conversion, the type name is ignored. Empty values can only be converted to and from other empty values. The following example shows this behavior:: // Create a boolean value representing 'true': AnyValue my_true{true}; // Assign this boolean value to an integer AnyValue: AnyValue my_int{UnsignedInteger32Type}; // gets default value zero my_int = my_true; // my_int now contains the boolean value 'true' (type changed) // Array element assignment: AnyValue my_array = ArrayValue({0, 1, 2, 3}); my_array[1] = my_true; // The second array element now contains the value '1', converted from // boolean value 'true'. AnyValue my_struct{{ {"a": {UnsignedInteger8Type, 42}} }}; my_array[0] = my_struct; // ERROR! Throws InvalidConversionException. Query methods ------------- The ``AnyValue`` API contains a number of methods for querying specific information about the value. These are listed here. .. function:: TypeCode AnyValue::GetTypeCode() const :return: TypeCode enumerator. Retrieve the typecode enumerator for this object. .. function:: AnyType AnyValue::GetType() const :return: ``AnyType`` of this object. Retrieve an ``AnyType`` object, representing this object's type. .. function:: std::string AnyValue::GetTypeName() const :return: Type name. Retrieve the type name. .. function:: std::vector AnyValue::MemberNames() const :return: List of member names. Return an ordered list of all direct member names. .. function:: std::size_t AnyValue::NumberOfMembers() const :return: Number of direct members for structured values and zero otherwise. Retrieve the number of direct members. This is always zero for non-structured values. .. function:: std::size_t AnyValue::NumberOfElements() const :return: Number of elements for an array value and zero otherwise. Retrieve the number of elements in the array. Returns zero when the current value is not an array value. .. function:: bool AnyValue::HasField(const std::string& fieldname) const :param fieldname: Name of the subvalue to search for. :return: ``true`` when a subvalue with the given fieldname exists. Check the presence of a subvalue with the given name. Composite fieldnames are supported. .. _conversion-methods: Conversion methods ------------------ During assigment and construction, a number of implicit conversions can take place. These always concern scalar types and are build onto the converting constructors and the following explicit conversion method: .. function:: template T AnyValue::As() const :return: The underlying value, cast to ``T``. :throws InvalidConversionException: When value couldn't be converted to ``T``. Specializations of this function template are explicitly declared and perform the required conversions: * Booleans to numeric values: zero for ``false`` and one for ``true``. * Numeric values to booleans: ``false`` if zero, ``true`` otherwise. * Numeric values to numeric values: standard conversion if the destination type can hold the source value. All other scalar conversions are not allowed and throw an exception. The :func:`AnyValue::As` method also supports a trivial cast to ``AnyValue``, which just returns a copy of the object. The following examples show the usage of this conversion method:: // Construct an unsigned 32 bit integer with value 19 and cast this to different types: AnyValue val{UnsignedInteger32Type, 19}; bool is_non_zero = val.as(); // is_non_zero is true int16 signed_val = val.as(); // signed_val is also 19 There is also a member function that tries to convert another ``AnyValue`` to the type of the current ``AnyValue``: .. function:: void AnyValue::ConvertFrom(const AnyValue& other) :param other: ``AnyValue`` object to convert from. :throws InvalidConversionException: If this operation could not successfully convert between the different types. Element access -------------- The ``AnyValue`` class overloads the index operators to provide a natural way to access element values of a structured value. Array values also support integer indices for element access. The overloaded operators are: .. function:: AnyValue& AnyValue::operator[](std::string fieldname) Try to retrieve a reference to the member that is identified by the fieldname. This fieldname can describe non-direct members by encoding the navigation to deeper lying members. A dot (``.``) is used to separate individual names of structure members, while square brackets are used to address array elements. :param fieldname: String encoding the path to a specific underlying value. :return: ``AnyValue`` object if member value was found. :throws InvalidOperationException: For values that do not support element access (empty or scalar values) or for fieldnames that cannot be correctly parsed/interpreted (wrong format or unknown key). .. function:: const AnyValue& AnyValue::operator[](std::string fieldname) const Const version of the previous operator overload. .. function:: AnyValue& AnyValue::operator[](std::size_t idx) This overload is only supported for array values and is provided as a convenience, while it doesn't require passing integer indices as string values. .. function:: const AnyValue& AnyValue::operator[](std::size_t idx) const Const version of the previous operator overload. Accessing direct elements of an array value can thus be achieved by passing either a string representation of the index or by passing it directly:: // Create array value of 5 booleans with default values: AnyType my_booleans{5, BooleanType}; // Change elements with index 2 and 3 to 'true': my_booleans["[2]"] = true; my_booleans[3] = true; Modifier methods ---------------- The ``AnyValue`` API provides modifier methods to extend structured or array values. These methods are mostly used for runtime creation of ``AnyValue`` objects, e.g. during parsing. Note that the effect of these methods includes a change of the underlying type. Hence these methods will throw an exception of type ``InvalidOperationException`` when called on an element of an array or one of its descendents. .. function:: AnyValue& AnyValue::AddMember(const std::string& name, const AnyValue& value) :param name: Member name to use. :param value: ``AnyValue`` object for the member value. :return: Reference to ``this`` to allow chaining such calls. :throws InvalidOperationException: If this operation is not supported (e.g. not a structured value or a structured array element). Add a member value for this structured value with the given name and value. Empty values are not allowed as member values. .. function:: AnyValue& AnyValue::AddElement(const AnyValue& value) :param value: ``AnyValue`` object to add as an element. :return: Reference to ``this`` to allow chaining such calls. :throws InvalidOperationException: If this operation is not supported (not an array value or an element of an array of arrays). Add an element to the end of this array value with the given value. Comparison operators -------------------- Simple comparison of ``AnyValue`` objects is supported by overloading both the equality and inequality operator: .. function:: bool AnyValue::operator==(const AnyValue& other) const :param other: Other ``AnyValue`` object to compare with the current. :return: ``true`` when equal, ``false`` otherwise. .. function:: bool AnyValue::operator!=(const AnyValue& other) const :param other: Other ``AnyValue`` object to compare with the current. :return: ``true`` when not equal, ``false`` otherwise. .. note:: Equality in the context of ``AnyValue`` objects requires compatibility of the embedded value leafs, rather than the more strict equality defined for ``AnyType`` objects: * Empty values are only equal to other empty values. * Scalar values are only equal when a successfull conversion of one value to the other's type is exactly equal to the other's value and the other way around (to assure symmetry). * Structured values are only equal to other structured values with the same type name, member names and values that compare equal. The order of members is also taken into account. * Array values are only equal to other array values with the same name, number of elements and elements that compare equal. In cases where a strict equality test is required, one could test equality of both types and values. A custom comparison function is also provided that handles arithmetic types (excluding 'bool', 'char8' and 'string'): .. function:: CompareResult Compare(const AnyValue& lhs, const AnyValue& rhs) :param lhs: ``AnyValue`` object on the left hand side. :param rhs: ``AnyValue`` object on the right hand side. :return: Enumerator indicating if lhs is less, greater, equivalent to rhs or if they are unordered. If one of the types is floating point, the comparison will use the largest floating point type among the given types as the common type ('float64' if there is one, 'float32' otherwise). Integral comparisons are only supported between types that are both signed or both unsigned. In these cases, the widest signed or unsigned integral type is used ('int64' or 'uint64'). For all comparisons that are not supported (for example 'string', structures, signed with unsigned, etc.), the function returns 'Unordered'. Global functions ---------------- Besides the serialization/parsing functions, the software module provides the following global functions: .. function:: bool TryConvert(AnyValue& dest, const AnyValue& src) :param dest: ``AnyValue`` object to assign to. :param src: ``AnyValue`` object to convert from. :return: ``true`` on successful conversion. Try to convert an AnyValue to another AnyValue. When conversion fails, the destination is left unchanged and false is returned. This is basically a non-throwing version of :func:`void AnyValue::ConvertFrom(const AnyValue&)`. .. function:: bool TryAssign(AnyValue& dest, const AnyValue& src) :param dest: ``AnyValue`` object to assign to. :param src: ``AnyValue`` object to assign from. :return: ``true`` on successful assignment. Try to assign an AnyValue to another AnyValue. When assignment or conversion fails, the destination is left unchanged and false is returned. This is basically a non-throwing version of :func:`AnyValue& AnyValue::operator=(const AnyValue&)`. .. function:: bool Increment(AnyValue& value) :param value: ``AnyValue`` object to increment. :return: ``true`` on successful increment. Try to increment (add 1) an AnyValue. This is only supported for arithmetic types (excluding 'bool', 'char8' and 'string'). In case of integer types, the behavior is defined to wrap around the maximum value to the minimum value (also for signed types). .. function:: bool Decrement(AnyValue& value) :param value: ``AnyValue`` object to decrement. :return: ``true`` on successful decrement. Try to decrement (subtract 1) an AnyValue. This is only supported for arithmetic types (excluding 'bool', 'char8' and 'string'). In case of integer types, the behavior is defined to wrap around the minimum value to the maximum value (also for signed types).