Exemple #1
0
        /// <summary>Parse string using the specified value type and return the resulting variant.</summary>
        public static Variant Parse(VariantType valueType, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                // Empty value
                return(new Variant());
            }
            else
            {
                // Switch on type of default value
                switch (valueType)
                {
                case VariantType.String:
                    return(new Variant(value));

                case VariantType.Double:
                    double doubleResult = double.Parse(value);
                    return(new Variant(doubleResult));

                case VariantType.Bool:
                    bool boolResult = bool.Parse(value);
                    return(new Variant(boolResult));

                case VariantType.Int:
                    int intResult = int.Parse(value);
                    return(new Variant(intResult));

                case VariantType.Long:
                    long longResult = long.Parse(value);
                    return(new Variant(longResult));

                case VariantType.LocalDate:
                    LocalDate dateResult = LocalDateUtil.Parse(value);
                    return(new Variant(dateResult));

                case VariantType.LocalTime:
                    LocalTime timeResult = LocalTimeUtil.Parse(value);
                    return(new Variant(timeResult));

                case VariantType.LocalMinute:
                    LocalMinute minuteResult = LocalMinuteUtil.Parse(value);
                    return(new Variant(minuteResult));

                case VariantType.LocalDateTime:
                    LocalDateTime dateTimeResult = LocalDateTimeUtil.Parse(value);
                    return(new Variant(dateTimeResult));

                case VariantType.Instant:
                    Instant instantResult = InstantUtil.Parse(value);
                    return(new Variant(instantResult));

                case VariantType.Enum:
                    throw new Exception("Variant cannot be created as enum without specifying enum typename.");

                default:
                    // Error message if any other type
                    throw new Exception("Unknown value type when parsing string into variant.");
                }
            }
        }
        /// <summary>
        /// Deserialize LocalMinute from readable int in ISO hhmm format.
        ///
        /// Null value is handled via [BsonIgnoreIfNull] attribute and is not expected here.
        /// </summary>
        public override LocalMinute Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            // LocalMinute is serialized as readable int in hhmm format
            int isoTime = context.Reader.ReadInt32();

            // Create LocalMinute object by parsing readable int
            var result = LocalMinuteUtil.FromIsoInt(isoTime);

            return(result);
        }
        /// <summary>Deserialize from data in ITreeReader.</summary>
        public void DeserializeFrom(ITreeReader reader)
        {
            // Do nothing if the selected XML node is empty
            if (reader == null)
            {
                return;
            }

            // Iterate over the list of elements
            var elementInfoList = DataTypeInfo.GetOrCreate(this).DataElements;

            foreach (var elementInfo in elementInfoList)
            {
                // Get element name and type
                string elementName = elementInfo.Name;
                Type   elementType = elementInfo.PropertyType;

                // Get inner XML node, continue with next element if null
                ITreeReader innerXmlNode = reader.ReadElement(elementName);
                if (innerXmlNode == null)
                {
                    continue;
                }

                // First check for each of the supported value types
                if (elementType == typeof(string))
                {
                    string token = innerXmlNode.ReadValue();
                    elementInfo.SetValue(this, token);
                }
                else if (elementType == typeof(double) || elementType == typeof(double?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = double.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(bool) || elementType == typeof(bool?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = bool.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(int) || elementType == typeof(int?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = int.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(long) || elementType == typeof(long?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = long.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = LocalDateUtil.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = LocalTimeUtil.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = LocalMinuteUtil.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = LocalDateTimeUtil.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = InstantUtil.Parse(token);
                    elementInfo.SetValue(this, value);
                }
                else if (elementType.IsSubclassOf(typeof(Enum)))
                {
                    string token = innerXmlNode.ReadValue();
                    var    value = Enum.Parse(elementType, token);
                    elementInfo.SetValue(this, value);
                }
                else
                {
                    // If none of the supported atomic types match, use the activator
                    // to create and empty instance of a complex type and populate it
                    var element = Activator.CreateInstance(elementType);
                    switch (element)
                    {
                    case IList listElement:
                        listElement.DeserializeFrom(elementName, reader);
                        break;

                    case Data dataElement:
                        var keyElement = dataElement as Key;
                        if (keyElement != null)
                        {
                            // Deserialize key from value node containing semicolon delimited string
                            string token = innerXmlNode.ReadValue();
                            // Parse semicolon delimited string to populate key elements
                            keyElement.PopulateFrom(token);
                        }
                        else
                        {
                            // Deserialize embedded data object from the contents of inner XML node
                            dataElement.DeserializeFrom(innerXmlNode);
                        }
                        break;

                    case TemporalId idElement:
                        // Do not serialize
                        break;

                    default:
                        // Error message if the type does not match any of the value or reference types
                        throw new Exception($"Serialization is not supported for type {elementType}.");
                    }

                    // Assign the populated key to the property
                    elementInfo.SetValue(this, element);
                }
            }
        }
Exemple #4
0
        /// <summary>Parse string using the specified value type and return the resulting variant.</summary>
        public static Variant Parse <T>(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                // Empty value
                return(new Variant());
            }
            else
            {
                // Switch on type of default value
                switch (default(T))
                {
                case string stringValue:
                    return(new Variant(value));

                case double doubleValue:
                    double doubleResult = double.Parse(value);
                    return(new Variant(doubleResult));

                case bool boolValue:
                    bool boolResult = bool.Parse(value);
                    return(new Variant(boolResult));

                case int intValue:
                    int intResult = int.Parse(value);
                    return(new Variant(intResult));

                case long longValue:
                    long longResult = long.Parse(value);
                    return(new Variant(longResult));

                case LocalDate dateValue:
                    LocalDate dateResult = LocalDateUtil.Parse(value);
                    return(new Variant(dateResult));

                case LocalTime timeValue:
                    LocalTime timeResult = LocalTimeUtil.Parse(value);
                    return(new Variant(timeResult));

                case LocalMinute minuteValue:
                    LocalMinute minuteResult = LocalMinuteUtil.Parse(value);
                    return(new Variant(minuteResult));

                case LocalDateTime dateTimeValue:
                    LocalDateTime dateTimeResult = LocalDateTimeUtil.Parse(value);
                    return(new Variant(dateTimeResult));

                case Instant instantValue:
                    Instant instantResult = InstantUtil.Parse(value);
                    return(new Variant(instantResult));

                case Enum enumValue:
                    object enumResult = Enum.Parse(typeof(T), value);
                    return(new Variant(enumResult));

                default:
                    // Error message if any other type
                    throw new Exception(GetWrongTypeErrorMessage(default(T)));
                }
            }
        }
Exemple #5
0
        //--- PRIVATE

        /// <summary>
        /// Populate key elements from an array of tokens starting
        /// at the specified token index. Elements that are themselves
        /// keys may use more than one token.
        ///
        /// This method returns the index of the first unused token.
        /// The returned value is the same as the length of the tokens
        /// array if all tokens are used.
        ///
        /// If key AKey has two elements, B and C, where
        ///
        /// * B has type BKey which has two string elements, and
        /// * C has type string,
        ///
        /// the semicolon delimited key has the following format:
        ///
        /// BToken1;BToken2;CToken
        ///
        /// To avoid serialization format uncertainty, key elements
        /// can have any atomic type except Double.
        /// </summary>
        private int PopulateFrom(string[] tokens, int tokenIndex)
        {
            // Get key elements using reflection
            var elementInfoArray = DataTypeInfo.GetOrCreate(this).DataElements;

            // If singleton is detected process it separately, then exit
            if (elementInfoArray.Length == 0)
            {
                // Check that string key is empty
                if (tokens.Length != 1 || tokens[0] != String.Empty)
                {
                    throw new Exception($"Type {GetType()} has key {string.Join(";", tokens)} while " +
                                        $"for a singleton the key must be an empty string (String.Empty). " +
                                        $"Singleton key is a key that has no key elements.");
                }

                // Return the length of empty key which consists of one (empty) token
                return(1);
            }

            // Check that there are enough remaining tokens in the key for each key element
            if (tokens.Length - tokenIndex < elementInfoArray.Length)
            {
                throw new Exception(
                          $"Key of type {GetType().Name} requires at least {elementInfoArray.Length} elements " +
                          $"{String.Join(";", elementInfoArray.Select(p => p.Name).ToArray())} while there are " +
                          $"only {tokens.Length - tokenIndex} remaining key tokens: {string.Join(";", tokens)}.");
            }

            // Iterate over element info elements, advancing tokenIndex by the required
            // number of tokens for each element. In case of embedded keys, the value of
            // tokenIndex is advanced by the recursive call to InitFromTokens method
            // of the embedded key.
            foreach (var elementInfo in elementInfoArray)
            {
                // Get element type
                Type elementType = elementInfo.PropertyType;

                // Convert string token to value depending on elementType
                if (elementType == typeof(string))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token = tokens[tokenIndex++];
                    elementInfo.SetValue(this, token);
                }
                else if (elementType == typeof(double) || elementType == typeof(double?))
                {
                    throw new Exception(
                              $"Key element {elementInfo.Name} has type Double. Elements of this type " +
                              $"cannot be part of key due to serialization format uncertainty.");
                }
                else if (elementType == typeof(bool) || elementType == typeof(bool?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    bool   tokenValue = bool.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(int) || elementType == typeof(int?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    int    tokenValue = int.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(long) || elementType == typeof(long?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    long   tokenValue = long.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDate is represented as readable int in
                    // non-delimited yyyymmdd format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalDate.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDate and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited yyyymmdd format.");
                    }

                    LocalDate tokenValue = LocalDateUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalTime is represented as readable int in
                    // non-delimited hhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalTime and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmmssfff format.");
                    }

                    LocalTime tokenValue = LocalTimeUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalMinute is represented as readable int in
                    // non-delimited hhmm format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalMinute and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmm format.");
                    }

                    LocalMinute tokenValue = LocalMinuteUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDateTime is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to LocalDateTime.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDateTime and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    LocalDateTime tokenValue = LocalDateTimeUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, Instant is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to Instant.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type Instant and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    Instant tokenValue = InstantUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(TemporalId) || elementType == typeof(TemporalId?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string     token      = tokens[tokenIndex++];
                    TemporalId tokenValue = TemporalId.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType.BaseType == typeof(Enum)) // TODO Support nullable Enum in key
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    object tokenValue = Enum.Parse(elementType, token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (typeof(Key).IsAssignableFrom(elementType))
                {
                    Key keyElement = (Key)Activator.CreateInstance(elementType);
                    tokenIndex = keyElement.PopulateFrom(tokens, tokenIndex);
                    elementInfo.SetValue(this, keyElement);
                }
                else
                {
                    // Field type is unsupported for a key, error message
                    throw new Exception(
                              $"Element {elementInfo.Name} of key type {GetType().Name} has type {elementType} that " +
                              $"is not one of the supported key element types. Available key element types are " +
                              $"string, bool, int, long, LocalDate, LocalTime, LocalMinute, LocalDateTime, Instant, Enum, or Key.");
                }
            }

            return(tokenIndex);
        }
        /// <summary>
        /// Write atomic value. Value type
        /// will be inferred from object.GetType().
        /// </summary>
        public void WriteValue(object value)
        {
            // Check state transition matrix
            if (currentState_ == TreeWriterState.ValueStarted)
            {
                currentState_ = TreeWriterState.ValueWritten;
            }
            else if (currentState_ == TreeWriterState.ValueArrayItemStarted)
            {
                currentState_ = TreeWriterState.ValueArrayItemWritten;
            }
            else
            {
                throw new Exception(
                          $"A call to WriteEndValue(...) does not follow a matching WriteValue(...) at the same indent level.");
            }

            // Check that we are either inside dictionary or array
            Type elementType = null;

            if (currentArray_ != null)
            {
                elementType = currentArrayItemType_;
            }
            else if (currentDict_ != null)
            {
                elementType = currentElementInfo_.PropertyType;
            }
            else
            {
                throw new Exception($"Cannot WriteValue(...)for element {currentElementName_} " +
                                    $"is called outside dictionary or array.");
            }

            if (value.IsEmpty())
            {
                // Do not record null or empty value into dictionary, but add it to an array
                // Add to dictionary or array, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = null;
                }
                return;
            }

            // Write based on element type
            Type valueType = value.GetType();

            if (elementType == typeof(string) ||
                elementType == typeof(double) || elementType == typeof(double?) ||
                elementType == typeof(bool) || elementType == typeof(bool?) ||
                elementType == typeof(int) || elementType == typeof(int?) ||
                elementType == typeof(long) || elementType == typeof(long?))
            {
                // Check type match
                if (!elementType.IsAssignableFrom(valueType))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into element of type {elementType.Name}.");
                }

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = value;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, value);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalDate; type should be int32.");
                }

                // Deserialize LocalDate as ISO int in yyyymmdd format
                LocalDate dateValue = LocalDateUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = dateValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, dateValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalTime; type should be int32.");
                }

                // Deserialize LocalTime as ISO int in hhmmssfff format
                LocalTime timeValue = LocalTimeUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = timeValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, timeValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalMinute; type should be int32.");
                }

                // Deserialize LocalTime as ISO int in hhmmssfff format
                LocalMinute minuteValue = LocalMinuteUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = minuteValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, minuteValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
            {
                // Check type match
                if (valueType != typeof(long))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalDateTime; type should be int64.");
                }

                // Deserialize LocalDateTime as ISO long in yyyymmddhhmmssfff format
                LocalDateTime dateTimeValue = LocalDateTimeUtil.FromIsoLong((long)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = dateTimeValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, dateTimeValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
            {
                // Check type match
                if (valueType != typeof(long))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into Instant; type should be int64.");
                }

                // Deserialize Instant as ISO long in yyyymmddhhmmssfff format
                Instant instantValue = InstantUtil.FromIsoLong((long)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = instantValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, instantValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType.IsEnum)
            {
                // Check type match
                if (valueType != typeof(string))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into enum {elementType.Name}; type should be string.");
                }

                string stringValue = (string)value;

                // Deserialize enum as string
                string enumString = (string)value;
                object enumValue  = Enum.Parse(elementType, enumString);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = enumValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, enumValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else
            {
                // We run out of value types at this point, now we can create
                // a reference type and check that it implements Key
                object keyObj = (Key)Activator.CreateInstance(elementType);
                if (keyObj is Key)
                {
                    Key key = (Key)keyObj;

                    // Check type match
                    if (valueType != typeof(string) && valueType != elementType)
                    {
                        throw new Exception(
                                  $"Attempting to deserialize value of type {valueType.Name} " +
                                  $"into key type {elementType.Name}; keys should be serialized into semicolon delimited string.");
                    }

                    // Populate by parsing semicolon delimited string
                    string stringValue = value.AsString();
                    key.PopulateFrom(stringValue);

                    // Add to array or dictionary, depending on what we are inside of
                    if (currentArray_ != null)
                    {
                        currentArray_[currentArray_.Count - 1] = key;
                    }
                    else if (currentDict_ != null)
                    {
                        currentElementInfo_.SetValue(currentDict_, key);
                    }
                    else
                    {
                        throw new Exception($"Value can only be added to a dictionary or array.");
                    }
                }
                else
                {
                    // Argument type is unsupported, error message
                    throw new Exception($"Element type {value.GetType()} is not supported for serialization.");
                }
            }
        }
        /// <summary>Deserialize by reading element from the tree reader.</summary>
        public static void DeserializeFrom(this IList obj, string elementName, ITreeReader reader)
        {
            // Check if the list is null
            if (obj == null)
            {
                throw new Exception("List passed to DeserializeFrom method for element {elementName} is null.");
            }

            // If the list is not empty, error message because the
            // object must be empty prior to parsing
            if (obj.Count > 0)
            {
                throw new Exception("List passed to DeserializeFrom method for element {elementName} is not empty.");
            }

            // Get item type from list type using reflection
            Type listType = obj.GetType();

            if (!listType.IsGenericType)
            {
                throw new Exception(
                          $"Type {listType} cannot be serialized because it implements only IList but not IList<T>.");
            }
            Type[] genericParameterTypes = listType.GenericTypeArguments;
            if (genericParameterTypes.Length != 1)
            {
                throw new Exception(
                          $"Generic parameter type list {genericParameterTypes} has more than " +
                          $"one element creating an ambiguity for deserialization code.");
            }
            Type itemType = genericParameterTypes[0];

            // Select XML node list with the specified name and iterate over XML nodes
            IEnumerable <ITreeReader> selectedXmlNodes = reader.ReadElements(elementName);

            foreach (ITreeReader selectedXmlNode in selectedXmlNodes)
            {
                if (selectedXmlNode == null)
                {
                    // Add null value
                    obj.Add(null);
                }
                else
                {
                    // Switch on item type
                    if (itemType == typeof(string))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            obj.Add(token);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(double) || itemType == typeof(double?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = double.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(bool) || itemType == typeof(bool?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = bool.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(int) || itemType == typeof(int?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = int.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(long) || itemType == typeof(long?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = long.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(LocalDate) || itemType == typeof(LocalDate?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = LocalDateUtil.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(LocalTime) || itemType == typeof(LocalTime?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = LocalTimeUtil.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(LocalMinute) || itemType == typeof(LocalMinute?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = LocalMinuteUtil.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(LocalDateTime) || itemType == typeof(LocalDateTime?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = LocalDateTimeUtil.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType == typeof(Instant) || itemType == typeof(Instant?))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = InstantUtil.Parse(token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else if (itemType.IsSubclassOf(typeof(Enum)))
                    {
                        string token = selectedXmlNode.ReadValue();
                        if (!string.IsNullOrEmpty(token))
                        {
                            var value = Enum.Parse(itemType, token);
                            obj.Add(value);
                        }
                        else
                        {
                            obj.Add(null);
                        }
                    }
                    else
                    {
                        // If none of the supported atomic types match, use the activator
                        // to create and empty instance of a complex type and populate it
                        var item = Activator.CreateInstance(itemType);
                        switch (item)
                        {
                        case ICollection collectionItem:
                            throw new Exception($"Deserialization is not supported for element {elementName} " +
                                                $"which is collection containing another collection.");

                        case Data dataItem:
                            var keyItem = dataItem as Key;
                            if (keyItem != null)
                            {
                                string token = selectedXmlNode.ReadValue();
                                if (!string.IsNullOrEmpty(token))
                                {
                                    // Parse semicolon delimited token to populate key item
                                    keyItem.PopulateFrom(token);
                                    obj.Add(item);
                                }
                                else
                                {
                                    obj.Add(null);
                                }
                            }
                            else
                            {
                                // Deserialize data item
                                dataItem.DeserializeFrom(selectedXmlNode);
                                obj.Add(item);
                            }
                            break;

                        default:
                            // Error message if the type does not match any of the value or reference types
                            throw new Exception($"Serialization is not supported for type {itemType}.");
                        }
                    }
                }
            }
        }