/// <summary>Convert LocalMinute to ISO 8601 4 digit int hhmm format.</summary> public static int ToIsoInt(this LocalMinute value) { // Serialized to one minute precision in ISO 8601 4 digit int hhmm format int result = value.Hour * 100 + value.Minute; return(result); }
/// <summary>Convert LocalMinute to ISO 8601 string in hh:mm format.</summary> public static string ToIsoString(this LocalMinute value) { // LocalMinute is serialized to ISO 8601 string in hh:mm format string result = String.Join(":", value.Hour.ToString("00"), value.Minute.ToString("00")); return(result); }
/// <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> /// Convert LocalTime to LocalMinute. /// /// Error message unless the local time falls exactly on the minute to nanosecond precision /// </summary> public static LocalMinute ToLocalMinute(this LocalTime value) { // Check if milliseconds is zero (that will be visible when LocalTime is serialized) if (value.Second != 0) { throw new Exception( $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero seconds."); } if (value.Millisecond != 0) { throw new Exception( $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero milliseconds."); } // Check if nanoseconds are present if (value.NanosecondOfSecond != 0) { throw new Exception( $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero nanoseconds of {value.NanosecondOfSecond}."); } LocalMinute result = new LocalMinute(value.Hour, value.Minute); return(result); }
/// <summary>Parse ISO 8601 4 digit int in hhmm format, throw if invalid format.</summary> public static LocalMinute FromIsoInt(int value) { // Extract int hour = value / 100; value -= hour * 100; int minute = value; // Create new LocalMinute object, validates values on input var result = new LocalMinute(hour, minute); return(result); }
/// <summary>Convert LocalMinute to variant.</summary> public static Variant ToVariant(this LocalMinute value) { return(new Variant(value)); }
/// <summary> /// Unlike LocalDate and LocalDateTime, the LocalMinute class /// has no special value that can be treated as Empty. /// Its default constructed value is 00:00 (midnight). /// </summary> public static bool HasValue(this LocalMinute value) { return(true); }
/// <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."); } } }