// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.Runtime.Serialization; using System.Text; using System.Xml; namespace System.Json { /// /// Represents a JavaScript Object Notation (JSON) primitive type in the common language runtime (CLR). /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "JsonPrimitive does not represent a collection.")] [DataContract] public sealed class JsonPrimitive : JsonValue { internal const string DateTimeIsoFormat = "yyyy-MM-ddTHH:mm:ss.fffK"; private const string UtcString = "UTC"; private const string GmtString = "GMT"; private static readonly long UnixEpochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; private static readonly char[] FloatingPointChars = new char[] { '.', 'e', 'E' }; private static readonly Type jsonPrimitiveType = typeof(JsonPrimitive); private static readonly Type uriType = typeof(Uri); private static readonly Dictionary> stringConverters = new Dictionary> { { typeof(bool), new Func(StringToBool) }, { typeof(byte), new Func(StringToByte) }, { typeof(char), new Func(StringToChar) }, { typeof(sbyte), new Func(StringToSByte) }, { typeof(short), new Func(StringToShort) }, { typeof(int), new Func(StringToInt) }, { typeof(long), new Func(StringToLong) }, { typeof(ushort), new Func(StringToUShort) }, { typeof(uint), new Func(StringToUInt) }, { typeof(ulong), new Func(StringToULong) }, { typeof(float), new Func(StringToFloat) }, { typeof(double), new Func(StringToDouble) }, { typeof(decimal), new Func(StringToDecimal) }, { typeof(DateTime), new Func(StringToDateTime) }, { typeof(DateTimeOffset), new Func(StringToDateTimeOffset) }, { typeof(Guid), new Func(StringToGuid) }, { typeof(Uri), new Func(StringToUri) }, }; [DataMember] private object value; [DataMember] private JsonType jsonType; /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(bool value) { jsonType = JsonType.Boolean; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(byte value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . [CLSCompliant(false)] public JsonPrimitive(sbyte value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(decimal value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(short value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . [CLSCompliant(false)] public JsonPrimitive(ushort value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(int value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . [CLSCompliant(false)] public JsonPrimitive(uint value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(long value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . [CLSCompliant(false)] public JsonPrimitive(ulong value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(float value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(double value) { jsonType = JsonType.Number; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . /// value is null. [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "This operator does not intend to represent a Uri overload.")] public JsonPrimitive(string value) { if (value == null) { throw new ArgumentNullException("value"); } jsonType = JsonType.String; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(char value) { jsonType = JsonType.String; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(DateTime value) { jsonType = JsonType.String; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(DateTimeOffset value) { jsonType = JsonType.String; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . /// value is null. public JsonPrimitive(Uri value) { if (value == null) { throw new ArgumentNullException("value"); } jsonType = JsonType.String; this.value = value; } /// /// Initializes a new instance of a type with a type. /// /// The object that initializes the new instance. /// A object stores a and the value used to initialize it. /// When initialized with a object, the is a , which can be /// recovered using the property. The value used to initialize the /// object can be recovered by casting the to . public JsonPrimitive(Guid value) { jsonType = JsonType.String; this.value = value; } private JsonPrimitive(object value, JsonType type) { jsonType = type; this.value = value; } private enum ReadAsFailureKind { NoFailure, InvalidCast, InvalidDateFormat, InvalidFormat, InvalidUriFormat, Overflow, } /// /// Gets the JsonType that is associated with this object. /// public override JsonType JsonType { get { return jsonType; } } /// /// Gets the value represented by this instance. /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Value in this context clearly refers to the underlying CLR value")] public object Value { get { return value; } } /// /// Attempts to create a instance from the specified value. /// /// The value to create the instance. /// The resulting instance on success, null otherwise. /// true if the operation is successful, false otherwise. public static bool TryCreate(object value, out JsonPrimitive result) { bool allowedType = true; JsonType jsonType = default(JsonType); if (value != null) { Type type = value.GetType(); switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: jsonType = JsonType.Boolean; break; case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Decimal: case TypeCode.Double: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Single: jsonType = JsonType.Number; break; case TypeCode.String: case TypeCode.Char: case TypeCode.DateTime: jsonType = JsonType.String; break; default: if (type == typeof(Uri) || type == typeof(Guid) || type == typeof(DateTimeOffset)) { jsonType = JsonType.String; } else { allowedType = false; } break; } } else { allowedType = false; } if (allowedType) { result = new JsonPrimitive(value, jsonType); return true; } else { result = null; return false; } } /// /// Attempts to convert this instance into an instance of the specified type. /// /// The type to which the conversion is being performed. /// An object instance initialized with the value /// specified if the conversion. /// If T is and this value does /// not represent a valid Uri. /// If T is a numeric type, and a narrowing conversion would result /// in a loss of data. For example, if this instance holds an value of 10000, /// and T is , this operation would throw an /// because 10000 is outside the range of the data type. /// If the conversion from the string representation of this /// value into another fails because the string is not in the proper format. /// If this instance cannot be read as type T. public override object ReadAs(Type type) { if (type == null) { throw new ArgumentNullException("type"); } object result; ReadAsFailureKind failure = TryReadAsInternal(type, out result); if (failure == ReadAsFailureKind.NoFailure) { return result; } else { string valueStr = value.ToString(); string typeOfTName = type.Name; switch (failure) { case ReadAsFailureKind.InvalidFormat: throw new FormatException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName)); case ReadAsFailureKind.InvalidDateFormat: throw new FormatException(RS.Format(Properties.Resources.InvalidDateFormat, valueStr, typeOfTName)); case ReadAsFailureKind.InvalidUriFormat: throw new UriFormatException(RS.Format(Properties.Resources.InvalidUriFormat, jsonPrimitiveType.Name, valueStr, typeOfTName, uriType.Name)); case ReadAsFailureKind.Overflow: throw new OverflowException(RS.Format(Properties.Resources.OverflowReadAs, valueStr, typeOfTName)); case ReadAsFailureKind.InvalidCast: default: throw new InvalidCastException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName)); } } } /// /// Attempts to convert this instance into an instance of the specified type. /// /// The type to which the conversion is being performed. /// An object instance to be initialized with this instance or null if the conversion cannot be performed. /// true if this instance can be read as the specified type; otherwise, false. [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")] public override bool TryReadAs(Type type, out object value) { return TryReadAsInternal(type, out value) == ReadAsFailureKind.NoFailure; } /// /// Returns the value this object wraps (if any). /// /// The value wrapped by this instance or null if none. internal override object Read() { return value; } internal override void Save(XmlDictionaryWriter jsonWriter) { if (value == null) { throw new ArgumentNullException("jsonWriter"); } switch (jsonType) { case JsonType.Boolean: jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.BooleanAttributeValue); break; case JsonType.Number: jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.NumberAttributeValue); break; default: jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.StringAttributeValue); break; } WriteValue(jsonWriter); } private static ConvertResult StringToBool(string valueString) { ConvertResult result = new ConvertResult(); bool tempBool; result.ReadAsFailureKind = Boolean.TryParse(valueString, out tempBool) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; result.Value = tempBool; return result; } private static ConvertResult StringToByte(string valueString) { ConvertResult result = new ConvertResult(); byte tempByte; result.ReadAsFailureKind = Byte.TryParse(valueString, out tempByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempByte); } result.Value = tempByte; return result; } private static ConvertResult StringToChar(string valueString) { ConvertResult result = new ConvertResult(); char tempChar; result.ReadAsFailureKind = Char.TryParse(valueString, out tempChar) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; result.Value = tempChar; return result; } private static ConvertResult StringToDecimal(string valueString) { ConvertResult result = new ConvertResult(); decimal tempDecimal; result.ReadAsFailureKind = Decimal.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDecimal) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempDecimal); } result.Value = tempDecimal; return result; } private static ConvertResult StringToDateTime(string valueString) { ConvertResult result = new ConvertResult(); DateTime tempDateTime; result.ReadAsFailureKind = TryParseDateTime(valueString, out tempDateTime) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat; result.Value = tempDateTime; return result; } private static ConvertResult StringToDateTimeOffset(string valueString) { ConvertResult result = new ConvertResult(); DateTimeOffset tempDateTimeOffset; result.ReadAsFailureKind = TryParseDateTimeOffset(valueString, out tempDateTimeOffset) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat; result.Value = tempDateTimeOffset; return result; } private static ConvertResult StringToDouble(string valueString) { ConvertResult result = new ConvertResult(); double tempDouble; result.ReadAsFailureKind = Double.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDouble) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempDouble); } result.Value = tempDouble; return result; } private static bool TryGuidParse (string value, out Guid guid) { return Guid.TryParse (value, out guid); } private static ConvertResult StringToGuid(string valueString) { ConvertResult result = new ConvertResult(); Guid tempGuid; result.ReadAsFailureKind = TryGuidParse(valueString, out tempGuid) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat; result.Value = tempGuid; return result; } private static ConvertResult StringToShort(string valueString) { ConvertResult result = new ConvertResult(); short tempShort; result.ReadAsFailureKind = Int16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempShort); } result.Value = tempShort; return result; } private static ConvertResult StringToInt(string valueString) { ConvertResult result = new ConvertResult(); int tempInt; result.ReadAsFailureKind = Int32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempInt); } result.Value = tempInt; return result; } private static ConvertResult StringToLong(string valueString) { ConvertResult result = new ConvertResult(); long tempLong; result.ReadAsFailureKind = Int64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempLong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempLong); } result.Value = tempLong; return result; } private static ConvertResult StringToSByte(string valueString) { ConvertResult result = new ConvertResult(); sbyte tempSByte; result.ReadAsFailureKind = SByte.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempSByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempSByte); } result.Value = tempSByte; return result; } private static ConvertResult StringToFloat(string valueString) { ConvertResult result = new ConvertResult(); float tempFloat; result.ReadAsFailureKind = Single.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempFloat) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempFloat); } result.Value = tempFloat; return result; } private static ConvertResult StringToUShort(string valueString) { ConvertResult result = new ConvertResult(); ushort tempUShort; result.ReadAsFailureKind = UInt16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempUShort); } result.Value = tempUShort; return result; } private static ConvertResult StringToUInt(string valueString) { ConvertResult result = new ConvertResult(); uint tempUInt; result.ReadAsFailureKind = UInt32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempUInt); } result.Value = tempUInt; return result; } private static ConvertResult StringToULong(string valueString) { ConvertResult result = new ConvertResult(); ulong tempULong; result.ReadAsFailureKind = UInt64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempULong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast; if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure) { result.ReadAsFailureKind = StringToNumberConverter(valueString, out tempULong); } result.Value = tempULong; return result; } private static ConvertResult StringToUri(string valueString) { ConvertResult result = new ConvertResult(); Uri tempUri; result.ReadAsFailureKind = Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out tempUri) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidUriFormat; result.Value = tempUri; return result; } private static ReadAsFailureKind StringToNumberConverter(string valueString, out T valueNumber) { string str = valueString.Trim(); if (str.IndexOfAny(FloatingPointChars) < 0) { long longVal; if (Int64.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out longVal)) { return NumberToNumberConverter(longVal, out valueNumber); } } decimal decValue; if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue) && decValue != 0) { return NumberToNumberConverter(decValue, out valueNumber); } double dblValue; if (Double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out dblValue)) { return NumberToNumberConverter(dblValue, out valueNumber); } valueNumber = default(T); return ReadAsFailureKind.InvalidFormat; } private static ReadAsFailureKind NumberToNumberConverter(object valueObject, out T valueNumber) { object value; ReadAsFailureKind failureKind = NumberToNumberConverter(typeof(T), valueObject, out value); if (failureKind == ReadAsFailureKind.NoFailure) { valueNumber = (T)value; } else { valueNumber = default(T); } return failureKind; } private static ReadAsFailureKind NumberToNumberConverter(Type type, object valueObject, out object valueNumber) { try { valueNumber = System.Convert.ChangeType(valueObject, type, CultureInfo.InvariantCulture); return ReadAsFailureKind.NoFailure; } catch (OverflowException) { valueNumber = null; return ReadAsFailureKind.Overflow; } } private static bool TryParseDateTime(string valueString, out DateTime dateTime) { string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString; if (DateTime.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime)) { return true; } if (TryParseAspNetDateTimeFormat(valueString, out dateTime)) { return true; } return false; } private static bool TryParseDateTimeOffset(string valueString, out DateTimeOffset dateTimeOffset) { string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString; if (DateTimeOffset.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) { return true; } if (TryParseAspNetDateTimeFormat(valueString, out dateTimeOffset)) { return true; } return false; } private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTime dateTime) { // Reference to the format is available at these sources: // http://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_sidebarb // http://msdn.microsoft.com/en-us/library/bb412170.aspx // The format for the value is given by the following regex: // \/Date\((?\-?\d+)(?[\+\-]?\d{4})\)\/ // where milliseconds is the number of milliseconds since 1970/01/01:00:00:00.000 UTC (the "unix baseline") // and offset is an optional which indicates whether the value is local or UTC. // The actual value of the offset is ignored, since the ticks represent the UTC offset. The value is converted to local time based on that info. const string DateTimePrefix = "/Date("; const int DateTimePrefixLength = 6; const string DateTimeSuffix = ")/"; const int DateTimeSuffixLength = 2; if (valueString.StartsWith(DateTimePrefix, StringComparison.Ordinal) && valueString.EndsWith(DateTimeSuffix, StringComparison.Ordinal)) { string ticksValue = valueString.Substring(DateTimePrefixLength, valueString.Length - DateTimePrefixLength - DateTimeSuffixLength); DateTimeKind dateTimeKind = DateTimeKind.Utc; int indexOfTimeZoneOffset = ticksValue.IndexOf('+', 1); if (indexOfTimeZoneOffset < 0) { indexOfTimeZoneOffset = ticksValue.IndexOf('-', 1); } // If an offset is present, verify it is properly formatted. Actual value is ignored (see spec). if (indexOfTimeZoneOffset != -1) { if (indexOfTimeZoneOffset + 5 == ticksValue.Length && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 1]) && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 2]) && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 3]) && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 4])) { ticksValue = ticksValue.Substring(0, indexOfTimeZoneOffset); dateTimeKind = DateTimeKind.Local; } else { dateTime = new DateTime(); return false; } } long millisecondsSinceUnixEpoch; if (Int64.TryParse(ticksValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out millisecondsSinceUnixEpoch)) { long ticks = (millisecondsSinceUnixEpoch * 10000) + UnixEpochTicks; if (ticks < DateTime.MaxValue.Ticks) { dateTime = new DateTime(ticks, DateTimeKind.Utc); if (dateTimeKind == DateTimeKind.Local) { dateTime = dateTime.ToLocalTime(); } return true; } } } dateTime = new DateTime(); return false; } private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTimeOffset dateTimeOffset) { DateTime dateTime; if (TryParseAspNetDateTimeFormat(valueString, out dateTime)) { dateTimeOffset = new DateTimeOffset(dateTime); return true; } dateTimeOffset = new DateTimeOffset(); return false; } private static bool IsLatinDigit(char c) { return (c >= '0') && (c <= '9'); } private static string UnescapeJsonString(string val) { if (val == null) { return null; } StringBuilder sb = null; int startIndex = 0, count = 0; for (int i = 0; i < val.Length; i++) { if (val[i] == '\\') { i++; if (sb == null) { sb = new StringBuilder(); } sb.Append(val, startIndex, count); Contract.Assert(i < val.Length, "Found that a '\' was the last character in a string, which is invalid JSON. Verify the calling method uses a valid JSON string as the input parameter of this method."); switch (val[i]) { case '"': case '\'': case '/': case '\\': sb.Append(val[i]); break; case 'b': sb.Append('\b'); break; case 'f': sb.Append('\f'); break; case 'n': sb.Append('\n'); break; case 'r': sb.Append('\r'); break; case 't': sb.Append('\t'); break; case 'u': Contract.Assert((i + 3) < val.Length, String.Format(CultureInfo.CurrentCulture, "Unexpected char {0} at position {1}. The unicode escape sequence should be followed by 4 digits.", val[i], i)); sb.Append(ParseChar(val.Substring(i + 1, 4), NumberStyles.HexNumber)); i += 4; break; } startIndex = i + 1; count = 0; } else { count++; } } if (sb == null) { return val; } if (count > 0) { sb.Append(val, startIndex, count); } return sb.ToString(); } private static char ParseChar(string value, NumberStyles style) { try { int intValue = Int32.Parse(value, style, NumberFormatInfo.InvariantInfo); return System.Convert.ToChar(intValue); } catch (ArgumentException exception) { throw new InvalidCastException(exception.Message, exception); } catch (FormatException exception) { throw new InvalidCastException(exception.Message, exception); } catch (OverflowException exception) { throw new InvalidCastException(exception.Message, exception); } } [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")] private ReadAsFailureKind TryReadAsInternal(Type type, out object value) { if (base.TryReadAs(type, out value)) { return ReadAsFailureKind.NoFailure; } if (type == this.value.GetType()) { value = this.value; return ReadAsFailureKind.NoFailure; } if (jsonType == JsonType.Number) { switch (Type.GetTypeCode(type)) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return NumberToNumberConverter(type, this.value, out value); case TypeCode.String: value = ToString(); return ReadAsFailureKind.NoFailure; } } if (jsonType == JsonType.Boolean) { if (type == typeof(string)) { value = ToString(); return ReadAsFailureKind.NoFailure; } } if (jsonType == JsonType.String) { string str = UnescapeJsonString(ToString()); Contract.Assert(str.Length >= 2 && str.StartsWith("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal), "The unescaped string must begin and end with quotes."); str = str.Substring(1, str.Length - 2); if (stringConverters.ContainsKey(type)) { ConvertResult result = stringConverters[type].Invoke(str); value = result.Value; return result.ReadAsFailureKind; } if (type == typeof(string)) { value = str; return ReadAsFailureKind.NoFailure; } } value = null; return ReadAsFailureKind.InvalidCast; } private void WriteValue(XmlDictionaryWriter jsonWriter) { Type valueType = value.GetType(); switch (Type.GetTypeCode(valueType)) { case TypeCode.Boolean: jsonWriter.WriteValue((bool)value); break; case TypeCode.Byte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.SByte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Decimal: jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0}", value)); break; case TypeCode.Single: case TypeCode.Double: jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0:R}", value)); break; case TypeCode.Char: jsonWriter.WriteValue(new string((char)value, 1)); break; case TypeCode.String: jsonWriter.WriteValue((string)value); break; case TypeCode.DateTime: jsonWriter.WriteValue(((DateTime)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture)); break; default: if (valueType == typeof(Uri)) { Uri uri = (Uri)value; jsonWriter.WriteValue(uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped)); } else if (valueType == typeof(DateTimeOffset)) { jsonWriter.WriteValue(((DateTimeOffset)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture)); } else { jsonWriter.WriteValue(value); } break; } } private class ConvertResult { public ReadAsFailureKind ReadAsFailureKind { get; set; } public object Value { get; set; } } } }