/// <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> /// Create a new instance of Instant from: /// /// Year, Month, Day, Hour, Minute, Second, and Millisecond /// /// in the specified timezone. /// /// Use timeZone = DateTimeZone.Utc for the UTC timezone. /// </summary> public static Instant Zoned(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeZone timeZone) { // Create local date from the specified fields var localDateTime = new LocalDateTime(year, month, day, hour, minute, second, millisecond); // Convert to Instant in the specified timezone var result = InstantUtil.Zoned(localDateTime, timeZone); return(result); }
/// <summary> /// Convert System.DateTime with Kind=Utc to Instant. /// /// Converts default constructed DateTime to Instant.Empty. /// /// Error message if the timezone is not UTC. /// </summary> public static Instant ToInstant(this DateTime value) { // Error message if the timezone is not UTC. if (value.Kind != DateTimeKind.Utc) { throw new Exception("DateTime can only be converted to Instant when its Kind=UTC."); } if (value != default) { // If not default constructed value, convert // to millisecond precision using fields return(InstantUtil.Utc( value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Millisecond)); } else { // Converts default constructed DateTime // to Instant.Empty return(InstantUtil.Empty); } }
/// <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); } } }
/// <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))); } } }
//--- 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}."); } } } } }