1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
3 using System.Collections.Generic;
4 using System.Diagnostics.CodeAnalysis;
5 using System.Diagnostics.Contracts;
6 using System.Globalization;
7 using System.Runtime.Serialization;
14 /// Represents a JavaScript Object Notation (JSON) primitive type in the common language runtime (CLR).
16 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
17 Justification = "JsonPrimitive does not represent a collection.")]
19 public sealed class JsonPrimitive : JsonValue
21 internal const string DateTimeIsoFormat = "yyyy-MM-ddTHH:mm:ss.fffK";
22 private const string UtcString = "UTC";
23 private const string GmtString = "GMT";
24 private static readonly long UnixEpochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
25 private static readonly char[] FloatingPointChars = new char[] { '.', 'e', 'E' };
26 private static readonly Type jsonPrimitiveType = typeof(JsonPrimitive);
27 private static readonly Type uriType = typeof(Uri);
29 private static readonly Dictionary<Type, Func<string, ConvertResult>> stringConverters = new Dictionary<Type, Func<string, ConvertResult>>
31 { typeof(bool), new Func<string, ConvertResult>(StringToBool) },
32 { typeof(byte), new Func<string, ConvertResult>(StringToByte) },
33 { typeof(char), new Func<string, ConvertResult>(StringToChar) },
34 { typeof(sbyte), new Func<string, ConvertResult>(StringToSByte) },
35 { typeof(short), new Func<string, ConvertResult>(StringToShort) },
36 { typeof(int), new Func<string, ConvertResult>(StringToInt) },
37 { typeof(long), new Func<string, ConvertResult>(StringToLong) },
38 { typeof(ushort), new Func<string, ConvertResult>(StringToUShort) },
39 { typeof(uint), new Func<string, ConvertResult>(StringToUInt) },
40 { typeof(ulong), new Func<string, ConvertResult>(StringToULong) },
41 { typeof(float), new Func<string, ConvertResult>(StringToFloat) },
42 { typeof(double), new Func<string, ConvertResult>(StringToDouble) },
43 { typeof(decimal), new Func<string, ConvertResult>(StringToDecimal) },
44 { typeof(DateTime), new Func<string, ConvertResult>(StringToDateTime) },
45 { typeof(DateTimeOffset), new Func<string, ConvertResult>(StringToDateTimeOffset) },
46 { typeof(Guid), new Func<string, ConvertResult>(StringToGuid) },
47 { typeof(Uri), new Func<string, ConvertResult>(StringToUri) },
54 private JsonType jsonType;
57 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Boolean"/> type.
59 /// <param name="value">The <see cref="System.Boolean"/> object that initializes the new instance.</param>
60 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
61 /// When initialized with a <see cref="System.Boolean"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Boolean"/>, which can be
62 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
63 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Boolean"/>.</remarks>
64 public JsonPrimitive(bool value)
66 jsonType = JsonType.Boolean;
71 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Byte"/> type.
73 /// <param name="value">The <see cref="System.Byte"/> object that initializes the new instance.</param>
74 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
75 /// When initialized with a <see cref="System.Byte"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
76 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
77 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Byte"/>.</remarks>
78 public JsonPrimitive(byte value)
80 jsonType = JsonType.Number;
85 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.SByte"/> type.
87 /// <param name="value">The <see cref="System.SByte"/> object that initializes the new instance.</param>
88 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
89 /// When initialized with a <see cref="System.SByte"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
90 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
91 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.SByte"/>.</remarks>
93 public JsonPrimitive(sbyte value)
95 jsonType = JsonType.Number;
100 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Decimal"/> type.
102 /// <param name="value">The <see cref="System.Decimal"/> object that initializes the new instance.</param>
103 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
104 /// When initialized with a <see cref="System.Decimal"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
105 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
106 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Decimal"/>.</remarks>
107 public JsonPrimitive(decimal value)
109 jsonType = JsonType.Number;
114 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int16"/> type.
116 /// <param name="value">The <see cref="System.Int16"/> object that initializes the new instance.</param>
117 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
118 /// When initialized with a <see cref="System.Int16"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
119 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
120 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int16"/>.</remarks>
121 public JsonPrimitive(short value)
123 jsonType = JsonType.Number;
128 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt16"/> type.
130 /// <param name="value">The <see cref="System.UInt16"/> object that initializes the new instance.</param>
131 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
132 /// When initialized with a <see cref="System.UInt16"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
133 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
134 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt16"/>.</remarks>
135 [CLSCompliant(false)]
136 public JsonPrimitive(ushort value)
138 jsonType = JsonType.Number;
143 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int32"/> type.
145 /// <param name="value">The <see cref="System.Int32"/> object that initializes the new instance.</param>
146 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
147 /// When initialized with a <see cref="System.Int32"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
148 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
149 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int32"/>.</remarks>
150 public JsonPrimitive(int value)
152 jsonType = JsonType.Number;
157 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt32"/> type.
159 /// <param name="value">The <see cref="System.UInt32"/> object that initializes the new instance.</param>
160 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
161 /// When initialized with a <see cref="System.UInt32"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
162 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
163 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt32"/>.</remarks>
164 [CLSCompliant(false)]
165 public JsonPrimitive(uint value)
167 jsonType = JsonType.Number;
172 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Int64"/> type.
174 /// <param name="value">The <see cref="System.Int64"/> object that initializes the new instance.</param>
175 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
176 /// When initialized with a <see cref="System.Int64"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
177 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
178 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Int64"/>.</remarks>
179 public JsonPrimitive(long value)
181 jsonType = JsonType.Number;
186 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.UInt64"/> type.
188 /// <param name="value">The <see cref="System.UInt64"/> object that initializes the new instance.</param>
189 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
190 /// When initialized with a <see cref="System.UInt64"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
191 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
192 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.UInt64"/>.</remarks>
193 [CLSCompliant(false)]
194 public JsonPrimitive(ulong value)
196 jsonType = JsonType.Number;
201 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Single"/> type.
203 /// <param name="value">The <see cref="System.Single"/> object that initializes the new instance.</param>
204 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
205 /// When initialized with a <see cref="System.Single"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
206 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
207 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Single"/>.</remarks>
208 public JsonPrimitive(float value)
210 jsonType = JsonType.Number;
215 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Double"/> type.
217 /// <param name="value">The <see cref="System.Double"/> object that initializes the new instance.</param>
218 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
219 /// When initialized with a <see cref="System.Double"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.Number"/>, which can be
220 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
221 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Double"/>.</remarks>
222 public JsonPrimitive(double value)
224 jsonType = JsonType.Number;
229 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.String"/> type.
231 /// <param name="value">The <see cref="System.String"/> object that initializes the new instance.</param>
232 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
233 /// When initialized with a <see cref="System.String"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
234 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
235 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.String"/>.</remarks>
236 /// <exception cref="System.ArgumentNullException">value is null.</exception>
237 [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads",
238 Justification = "This operator does not intend to represent a Uri overload.")]
239 public JsonPrimitive(string value)
243 throw new ArgumentNullException("value");
246 jsonType = JsonType.String;
251 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Char"/> type.
253 /// <param name="value">The <see cref="System.Char"/> object that initializes the new instance.</param>
254 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
255 /// When initialized with a <see cref="System.Char"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
256 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
257 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Char"/>.</remarks>
258 public JsonPrimitive(char value)
260 jsonType = JsonType.String;
265 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.DateTime"/> type.
267 /// <param name="value">The <see cref="System.DateTime"/> object that initializes the new instance.</param>
268 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
269 /// When initialized with a <see cref="System.DateTime"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
270 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
271 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.DateTime"/>.</remarks>
272 public JsonPrimitive(DateTime value)
274 jsonType = JsonType.String;
279 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.DateTimeOffset"/> type.
281 /// <param name="value">The <see cref="System.DateTimeOffset"/> object that initializes the new instance.</param>
282 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
283 /// When initialized with a <see cref="System.DateTimeOffset"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
284 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
285 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.DateTimeOffset"/>.</remarks>
286 public JsonPrimitive(DateTimeOffset value)
288 jsonType = JsonType.String;
293 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Uri"/> type.
295 /// <param name="value">The <see cref="System.Uri"/> object that initializes the new instance.</param>
296 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
297 /// When initialized with a <see cref="System.Uri"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
298 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
299 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Uri"/>.</remarks>
300 /// <exception cref="System.ArgumentNullException">value is null.</exception>
301 public JsonPrimitive(Uri value)
305 throw new ArgumentNullException("value");
308 jsonType = JsonType.String;
313 /// Initializes a new instance of a <see cref="System.Json.JsonPrimitive"/> type with a <see cref="System.Guid"/> type.
315 /// <param name="value">The <see cref="System.Guid"/> object that initializes the new instance.</param>
316 /// <remarks>A <see cref="System.Json.JsonPrimitive"/> object stores a <see cref="System.Json.JsonType"/> and the value used to initialize it.
317 /// When initialized with a <see cref="System.Guid"/> object, the <see cref="System.Json.JsonType"/> is a <see cref="F:System.Json.JsonType.String"/>, which can be
318 /// recovered using the <see cref="System.Json.JsonPrimitive.JsonType"/> property. The value used to initialize the <see cref="System.Json.JsonPrimitive"/>
319 /// object can be recovered by casting the <see cref="System.Json.JsonPrimitive"/> to <see cref="System.Guid"/>.</remarks>
320 public JsonPrimitive(Guid value)
322 jsonType = JsonType.String;
326 private JsonPrimitive(object value, JsonType type)
332 private enum ReadAsFailureKind
343 /// Gets the JsonType that is associated with this <see cref="System.Json.JsonPrimitive"/> object.
345 public override JsonType JsonType
347 get { return jsonType; }
351 /// Gets the value represented by this instance.
353 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
354 Justification = "Value in this context clearly refers to the underlying CLR value")]
357 get { return value; }
361 /// Attempts to create a <see cref="JsonPrimitive"/> instance from the specified <see cref="object"/> value.
363 /// <param name="value">The <see cref="object"/> value to create the <see cref="JsonPrimitive"/> instance.</param>
364 /// <param name="result">The resulting <see cref="JsonPrimitive"/> instance on success, null otherwise.</param>
365 /// <returns>true if the operation is successful, false otherwise.</returns>
366 public static bool TryCreate(object value, out JsonPrimitive result)
368 bool allowedType = true;
369 JsonType jsonType = default(JsonType);
373 Type type = value.GetType();
374 switch (Type.GetTypeCode(type))
376 case TypeCode.Boolean:
377 jsonType = JsonType.Boolean;
381 case TypeCode.Decimal:
382 case TypeCode.Double:
386 case TypeCode.UInt16:
387 case TypeCode.UInt32:
388 case TypeCode.UInt64:
389 case TypeCode.Single:
390 jsonType = JsonType.Number;
392 case TypeCode.String:
394 case TypeCode.DateTime:
395 jsonType = JsonType.String;
398 if (type == typeof(Uri) || type == typeof(Guid) || type == typeof(DateTimeOffset))
400 jsonType = JsonType.String;
417 result = new JsonPrimitive(value, jsonType);
428 /// Attempts to convert this <see cref="System.Json.JsonPrimitive"/> instance into an instance of the specified type.
430 /// <param name="type">The type to which the conversion is being performed.</param>
431 /// <returns>An object instance initialized with the <see cref="System.Json.JsonValue"/> value
432 /// specified if the conversion.</returns>
433 /// <exception cref="System.UriFormatException">If T is <see cref="System.Uri"/> and this value does
434 /// not represent a valid Uri.</exception>
435 /// <exception cref="OverflowException">If T is a numeric type, and a narrowing conversion would result
436 /// in a loss of data. For example, if this instance holds an <see cref="System.Int32"/> value of 10000,
437 /// and T is <see cref="System.Byte"/>, this operation would throw an <see cref="System.OverflowException"/>
438 /// because 10000 is outside the range of the <see cref="System.Byte"/> data type.</exception>
439 /// <exception cref="System.FormatException">If the conversion from the string representation of this
440 /// value into another fails because the string is not in the proper format.</exception>
441 /// <exception cref="System.InvalidCastException">If this instance cannot be read as type T.</exception>
442 public override object ReadAs(Type type)
446 throw new ArgumentNullException("type");
450 ReadAsFailureKind failure = TryReadAsInternal(type, out result);
451 if (failure == ReadAsFailureKind.NoFailure)
457 string valueStr = value.ToString();
458 string typeOfTName = type.Name;
461 case ReadAsFailureKind.InvalidFormat:
462 throw new FormatException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName));
463 case ReadAsFailureKind.InvalidDateFormat:
464 throw new FormatException(RS.Format(Properties.Resources.InvalidDateFormat, valueStr, typeOfTName));
465 case ReadAsFailureKind.InvalidUriFormat:
466 throw new UriFormatException(RS.Format(Properties.Resources.InvalidUriFormat, jsonPrimitiveType.Name, valueStr, typeOfTName, uriType.Name));
467 case ReadAsFailureKind.Overflow:
468 throw new OverflowException(RS.Format(Properties.Resources.OverflowReadAs, valueStr, typeOfTName));
469 case ReadAsFailureKind.InvalidCast:
471 throw new InvalidCastException(RS.Format(Properties.Resources.CannotReadPrimitiveAsType, valueStr, typeOfTName));
477 /// Attempts to convert this <see cref="System.Json.JsonPrimitive"/> instance into an instance of the specified type.
479 /// <param name="type">The type to which the conversion is being performed.</param>
480 /// <param name="value">An object instance to be initialized with this instance or null if the conversion cannot be performed.</param>
481 /// <returns>true if this <see cref="System.Json.JsonPrimitive"/> instance can be read as the specified type; otherwise, false.</returns>
482 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value",
483 Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")]
484 public override bool TryReadAs(Type type, out object value)
486 return TryReadAsInternal(type, out value) == ReadAsFailureKind.NoFailure;
490 /// Returns the value this object wraps (if any).
492 /// <returns>The value wrapped by this instance or null if none.</returns>
493 internal override object Read()
498 internal override void Save(XmlDictionaryWriter jsonWriter)
502 throw new ArgumentNullException("jsonWriter");
507 case JsonType.Boolean:
508 jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.BooleanAttributeValue);
510 case JsonType.Number:
511 jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.NumberAttributeValue);
514 jsonWriter.WriteAttributeString(JXmlToJsonValueConverter.TypeAttributeName, JXmlToJsonValueConverter.StringAttributeValue);
518 WriteValue(jsonWriter);
521 private static ConvertResult StringToBool(string valueString)
523 ConvertResult result = new ConvertResult();
525 result.ReadAsFailureKind = Boolean.TryParse(valueString, out tempBool) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat;
526 result.Value = tempBool;
530 private static ConvertResult StringToByte(string valueString)
532 ConvertResult result = new ConvertResult();
534 result.ReadAsFailureKind = Byte.TryParse(valueString, out tempByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
535 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
537 result.ReadAsFailureKind = StringToNumberConverter<byte>(valueString, out tempByte);
540 result.Value = tempByte;
544 private static ConvertResult StringToChar(string valueString)
546 ConvertResult result = new ConvertResult();
548 result.ReadAsFailureKind = Char.TryParse(valueString, out tempChar) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat;
549 result.Value = tempChar;
553 private static ConvertResult StringToDecimal(string valueString)
555 ConvertResult result = new ConvertResult();
557 result.ReadAsFailureKind = Decimal.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDecimal) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
558 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
560 result.ReadAsFailureKind = StringToNumberConverter<decimal>(valueString, out tempDecimal);
563 result.Value = tempDecimal;
567 private static ConvertResult StringToDateTime(string valueString)
569 ConvertResult result = new ConvertResult();
570 DateTime tempDateTime;
571 result.ReadAsFailureKind = TryParseDateTime(valueString, out tempDateTime) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat;
572 result.Value = tempDateTime;
576 private static ConvertResult StringToDateTimeOffset(string valueString)
578 ConvertResult result = new ConvertResult();
579 DateTimeOffset tempDateTimeOffset;
580 result.ReadAsFailureKind = TryParseDateTimeOffset(valueString, out tempDateTimeOffset) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidDateFormat;
581 result.Value = tempDateTimeOffset;
585 private static ConvertResult StringToDouble(string valueString)
587 ConvertResult result = new ConvertResult();
589 result.ReadAsFailureKind = Double.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempDouble) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
590 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
592 result.ReadAsFailureKind = StringToNumberConverter<double>(valueString, out tempDouble);
595 result.Value = tempDouble;
599 private static bool TryGuidParse (string value, out Guid guid)
602 return Guid.TryParse (value, out guid);
605 guid = new Guid (value);
607 } catch (Exception) {
614 private static ConvertResult StringToGuid(string valueString)
616 ConvertResult result = new ConvertResult();
618 result.ReadAsFailureKind = TryGuidParse(valueString, out tempGuid) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidFormat;
619 result.Value = tempGuid;
623 private static ConvertResult StringToShort(string valueString)
625 ConvertResult result = new ConvertResult();
627 result.ReadAsFailureKind = Int16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
628 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
630 result.ReadAsFailureKind = StringToNumberConverter<short>(valueString, out tempShort);
633 result.Value = tempShort;
637 private static ConvertResult StringToInt(string valueString)
639 ConvertResult result = new ConvertResult();
641 result.ReadAsFailureKind = Int32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
642 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
644 result.ReadAsFailureKind = StringToNumberConverter<int>(valueString, out tempInt);
647 result.Value = tempInt;
651 private static ConvertResult StringToLong(string valueString)
653 ConvertResult result = new ConvertResult();
655 result.ReadAsFailureKind = Int64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempLong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
656 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
658 result.ReadAsFailureKind = StringToNumberConverter<long>(valueString, out tempLong);
661 result.Value = tempLong;
665 private static ConvertResult StringToSByte(string valueString)
667 ConvertResult result = new ConvertResult();
669 result.ReadAsFailureKind = SByte.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempSByte) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
670 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
672 result.ReadAsFailureKind = StringToNumberConverter<sbyte>(valueString, out tempSByte);
675 result.Value = tempSByte;
679 private static ConvertResult StringToFloat(string valueString)
681 ConvertResult result = new ConvertResult();
683 result.ReadAsFailureKind = Single.TryParse(valueString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out tempFloat) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
684 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
686 result.ReadAsFailureKind = StringToNumberConverter<float>(valueString, out tempFloat);
689 result.Value = tempFloat;
693 private static ConvertResult StringToUShort(string valueString)
695 ConvertResult result = new ConvertResult();
697 result.ReadAsFailureKind = UInt16.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUShort) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
698 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
700 result.ReadAsFailureKind = StringToNumberConverter<ushort>(valueString, out tempUShort);
703 result.Value = tempUShort;
707 private static ConvertResult StringToUInt(string valueString)
709 ConvertResult result = new ConvertResult();
711 result.ReadAsFailureKind = UInt32.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempUInt) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
712 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
714 result.ReadAsFailureKind = StringToNumberConverter<uint>(valueString, out tempUInt);
717 result.Value = tempUInt;
721 private static ConvertResult StringToULong(string valueString)
723 ConvertResult result = new ConvertResult();
725 result.ReadAsFailureKind = UInt64.TryParse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out tempULong) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidCast;
726 if (result.ReadAsFailureKind != ReadAsFailureKind.NoFailure)
728 result.ReadAsFailureKind = StringToNumberConverter<ulong>(valueString, out tempULong);
731 result.Value = tempULong;
735 private static ConvertResult StringToUri(string valueString)
737 ConvertResult result = new ConvertResult();
739 result.ReadAsFailureKind = Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out tempUri) ? ReadAsFailureKind.NoFailure : ReadAsFailureKind.InvalidUriFormat;
740 result.Value = tempUri;
744 private static ReadAsFailureKind StringToNumberConverter<T>(string valueString, out T valueNumber)
746 string str = valueString.Trim();
748 if (str.IndexOfAny(FloatingPointChars) < 0)
751 if (Int64.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out longVal))
753 return NumberToNumberConverter<T>(longVal, out valueNumber);
758 if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue) && decValue != 0)
760 return NumberToNumberConverter<T>(decValue, out valueNumber);
764 if (Double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out dblValue))
766 return NumberToNumberConverter<T>(dblValue, out valueNumber);
769 valueNumber = default(T);
770 return ReadAsFailureKind.InvalidFormat;
773 private static ReadAsFailureKind NumberToNumberConverter<T>(object valueObject, out T valueNumber)
776 ReadAsFailureKind failureKind = NumberToNumberConverter(typeof(T), valueObject, out value);
777 if (failureKind == ReadAsFailureKind.NoFailure)
779 valueNumber = (T)value;
783 valueNumber = default(T);
789 private static ReadAsFailureKind NumberToNumberConverter(Type type, object valueObject, out object valueNumber)
793 valueNumber = System.Convert.ChangeType(valueObject, type, CultureInfo.InvariantCulture);
794 return ReadAsFailureKind.NoFailure;
796 catch (OverflowException)
799 return ReadAsFailureKind.Overflow;
803 private static bool TryParseDateTime(string valueString, out DateTime dateTime)
805 string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString;
807 if (DateTime.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime))
812 if (TryParseAspNetDateTimeFormat(valueString, out dateTime))
820 private static bool TryParseDateTimeOffset(string valueString, out DateTimeOffset dateTimeOffset)
822 string filteredValue = valueString.EndsWith(UtcString, StringComparison.Ordinal) ? valueString.Replace(UtcString, GmtString) : valueString;
824 if (DateTimeOffset.TryParse(filteredValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset))
829 if (TryParseAspNetDateTimeFormat(valueString, out dateTimeOffset))
837 private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTime dateTime)
839 // Reference to the format is available at these sources:
840 // http://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_sidebarb
841 // http://msdn.microsoft.com/en-us/library/bb412170.aspx
843 // The format for the value is given by the following regex:
844 // \/Date\((?<milliseconds>\-?\d+)(?<offset>[\+\-]?\d{4})\)\/
845 // where milliseconds is the number of milliseconds since 1970/01/01:00:00:00.000 UTC (the "unix baseline")
846 // and offset is an optional which indicates whether the value is local or UTC.
847 // 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.
848 const string DateTimePrefix = "/Date(";
849 const int DateTimePrefixLength = 6;
850 const string DateTimeSuffix = ")/";
851 const int DateTimeSuffixLength = 2;
853 if (valueString.StartsWith(DateTimePrefix, StringComparison.Ordinal) && valueString.EndsWith(DateTimeSuffix, StringComparison.Ordinal))
855 string ticksValue = valueString.Substring(DateTimePrefixLength, valueString.Length - DateTimePrefixLength - DateTimeSuffixLength);
856 DateTimeKind dateTimeKind = DateTimeKind.Utc;
858 int indexOfTimeZoneOffset = ticksValue.IndexOf('+', 1);
860 if (indexOfTimeZoneOffset < 0)
862 indexOfTimeZoneOffset = ticksValue.IndexOf('-', 1);
865 // If an offset is present, verify it is properly formatted. Actual value is ignored (see spec).
866 if (indexOfTimeZoneOffset != -1)
868 if (indexOfTimeZoneOffset + 5 == ticksValue.Length
869 && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 1])
870 && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 2])
871 && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 3])
872 && IsLatinDigit(ticksValue[indexOfTimeZoneOffset + 4]))
874 ticksValue = ticksValue.Substring(0, indexOfTimeZoneOffset);
875 dateTimeKind = DateTimeKind.Local;
879 dateTime = new DateTime();
884 long millisecondsSinceUnixEpoch;
885 if (Int64.TryParse(ticksValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out millisecondsSinceUnixEpoch))
887 long ticks = (millisecondsSinceUnixEpoch * 10000) + UnixEpochTicks;
888 if (ticks < DateTime.MaxValue.Ticks)
890 dateTime = new DateTime(ticks, DateTimeKind.Utc);
891 if (dateTimeKind == DateTimeKind.Local)
893 dateTime = dateTime.ToLocalTime();
901 dateTime = new DateTime();
905 private static bool TryParseAspNetDateTimeFormat(string valueString, out DateTimeOffset dateTimeOffset)
908 if (TryParseAspNetDateTimeFormat(valueString, out dateTime))
910 dateTimeOffset = new DateTimeOffset(dateTime);
914 dateTimeOffset = new DateTimeOffset();
918 private static bool IsLatinDigit(char c)
920 return (c >= '0') && (c <= '9');
923 private static string UnescapeJsonString(string val)
930 StringBuilder sb = null;
931 int startIndex = 0, count = 0;
932 for (int i = 0; i < val.Length; i++)
939 sb = new StringBuilder();
942 sb.Append(val, startIndex, count);
944 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.");
971 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));
973 sb.Append(ParseChar(val.Substring(i + 1, 4), NumberStyles.HexNumber));
994 sb.Append(val, startIndex, count);
997 return sb.ToString();
1000 private static char ParseChar(string value, NumberStyles style)
1004 int intValue = Int32.Parse(value, style, NumberFormatInfo.InvariantInfo);
1005 return System.Convert.ToChar(intValue);
1007 catch (ArgumentException exception)
1009 throw new InvalidCastException(exception.Message, exception);
1011 catch (FormatException exception)
1013 throw new InvalidCastException(exception.Message, exception);
1015 catch (OverflowException exception)
1017 throw new InvalidCastException(exception.Message, exception);
1021 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value",
1022 Justification = "field is used with 'this' and arg is out param which makes it harder to be misused.")]
1023 private ReadAsFailureKind TryReadAsInternal(Type type, out object value)
1025 if (base.TryReadAs(type, out value))
1027 return ReadAsFailureKind.NoFailure;
1030 if (type == this.value.GetType())
1033 return ReadAsFailureKind.NoFailure;
1036 if (jsonType == JsonType.Number)
1038 switch (Type.GetTypeCode(type))
1041 case TypeCode.SByte:
1042 case TypeCode.Int16:
1043 case TypeCode.Int32:
1044 case TypeCode.Int64:
1045 case TypeCode.UInt16:
1046 case TypeCode.UInt32:
1047 case TypeCode.UInt64:
1048 case TypeCode.Single:
1049 case TypeCode.Double:
1050 case TypeCode.Decimal:
1051 return NumberToNumberConverter(type, this.value, out value);
1052 case TypeCode.String:
1054 return ReadAsFailureKind.NoFailure;
1058 if (jsonType == JsonType.Boolean)
1060 if (type == typeof(string))
1063 return ReadAsFailureKind.NoFailure;
1067 if (jsonType == JsonType.String)
1069 string str = UnescapeJsonString(ToString());
1071 Contract.Assert(str.Length >= 2 && str.StartsWith("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal), "The unescaped string must begin and end with quotes.");
1073 str = str.Substring(1, str.Length - 2);
1075 if (stringConverters.ContainsKey(type))
1077 ConvertResult result = stringConverters[type].Invoke(str);
1078 value = result.Value;
1079 return result.ReadAsFailureKind;
1082 if (type == typeof(string))
1085 return ReadAsFailureKind.NoFailure;
1090 return ReadAsFailureKind.InvalidCast;
1093 private void WriteValue(XmlDictionaryWriter jsonWriter)
1095 Type valueType = value.GetType();
1096 switch (Type.GetTypeCode(valueType))
1098 case TypeCode.Boolean:
1099 jsonWriter.WriteValue((bool)value);
1102 case TypeCode.Int16:
1103 case TypeCode.Int32:
1104 case TypeCode.Int64:
1105 case TypeCode.SByte:
1106 case TypeCode.UInt16:
1107 case TypeCode.UInt32:
1108 case TypeCode.UInt64:
1109 case TypeCode.Decimal:
1110 jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0}", value));
1112 case TypeCode.Single:
1113 case TypeCode.Double:
1114 jsonWriter.WriteValue(String.Format(CultureInfo.InvariantCulture, "{0:R}", value));
1117 jsonWriter.WriteValue(new string((char)value, 1));
1119 case TypeCode.String:
1120 jsonWriter.WriteValue((string)value);
1122 case TypeCode.DateTime:
1123 jsonWriter.WriteValue(((DateTime)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture));
1126 if (valueType == typeof(Uri))
1128 Uri uri = (Uri)value;
1129 jsonWriter.WriteValue(uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped));
1131 else if (valueType == typeof(DateTimeOffset))
1133 jsonWriter.WriteValue(((DateTimeOffset)value).ToString(DateTimeIsoFormat, CultureInfo.InvariantCulture));
1137 jsonWriter.WriteValue(value);
1144 private class ConvertResult
1146 public ReadAsFailureKind ReadAsFailureKind { get; set; }
1148 public object Value { get; set; }