source: code/Website/dot-net-library/written-by-xiao-yifang/OpenFlashChart/JSON/JsonReader.cs@ 7849

Last change on this file since 7849 was 7849, checked in by dennisw, 15 years ago
File size: 46.8 KB
Line 
1#region License
2/*---------------------------------------------------------------------------------*\
3
4 Distributed under the terms of an MIT-style license:
5
6 The MIT License
7
8 Copyright (c) 2006-2009 Stephen M. McKamey
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
16
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 THE SOFTWARE.
27
28\*---------------------------------------------------------------------------------*/
29#endregion License
30
31using System;
32using System.ComponentModel;
33using System.Text;
34using System.Collections;
35using System.Collections.Generic;
36using System.Globalization;
37using System.IO;
38using System.Reflection;
39
40namespace JsonFx.Json
41{
42 /// <summary>
43 /// Reader for consuming JSON data
44 /// </summary>
45 public class JsonReader
46 {
47 #region Constants
48
49 internal const string LiteralFalse = "false";
50 internal const string LiteralTrue = "true";
51 internal const string LiteralNull = "null";
52 internal const string LiteralUndefined = "undefined";
53 internal const string LiteralNotANumber = "NaN";
54 internal const string LiteralPositiveInfinity = "Infinity";
55 internal const string LiteralNegativeInfinity = "-Infinity";
56
57 internal const char OperatorNegate = '-';
58 internal const char OperatorUnaryPlus = '+';
59 internal const char OperatorArrayStart = '[';
60 internal const char OperatorArrayEnd = ']';
61 internal const char OperatorObjectStart = '{';
62 internal const char OperatorObjectEnd = '}';
63 internal const char OperatorStringDelim = '"';
64 internal const char OperatorStringDelimAlt = '\'';
65 internal const char OperatorValueDelim = ',';
66 internal const char OperatorNameDelim = ':';
67 internal const char OperatorCharEscape = '\\';
68
69 private const string CommentStart = "/*";
70 private const string CommentEnd = "*/";
71 private const string CommentLine = "//";
72 private const string LineEndings = "\r\n";
73
74 internal const string TypeGenericIDictionary = "System.Collections.Generic.IDictionary`2";
75
76 private const string ErrorUnrecognizedToken = "Illegal JSON sequence.";
77 private const string ErrorUnterminatedComment = "Unterminated comment block.";
78 private const string ErrorUnterminatedObject = "Unterminated JSON object.";
79 private const string ErrorUnterminatedArray = "Unterminated JSON array.";
80 private const string ErrorUnterminatedString = "Unterminated JSON string.";
81 private const string ErrorIllegalNumber = "Illegal JSON number.";
82 private const string ErrorExpectedString = "Expected JSON string.";
83 private const string ErrorExpectedObject = "Expected JSON object.";
84 private const string ErrorExpectedArray = "Expected JSON array.";
85 private const string ErrorExpectedPropertyName = "Expected JSON object property name.";
86 private const string ErrorExpectedPropertyNameDelim = "Expected JSON object property name delimiter.";
87 internal const string ErrorGenericIDictionary = "Types which implement Generic IDictionary<TKey, TValue> also need to implement IDictionary to be deserialized. ({0})";
88 private const string ErrorGenericIDictionaryKeys = "Types which implement Generic IDictionary<TKey, TValue> need to have string keys to be deserialized. ({0})";
89
90 #endregion Constants
91
92 #region Fields
93
94 private readonly TypeCoercionUtility Coercion = new TypeCoercionUtility();
95 private readonly string Source = null;
96 private readonly int SourceLength = 0;
97 private string typeHintName;
98 private int index;
99
100 #endregion Fields
101
102 #region Init
103
104 /// <summary>
105 /// Ctor.
106 /// </summary>
107 /// <param name="input">TextReader containing source</param>
108 public JsonReader(TextReader input)
109 {
110 this.Source = input.ReadToEnd();
111 this.SourceLength = this.Source.Length;
112 }
113
114 /// <summary>
115 /// Ctor.
116 /// </summary>
117 /// <param name="input">Stream containing source</param>
118 public JsonReader(Stream input)
119 {
120 using (StreamReader reader = new StreamReader(input, true))
121 {
122 this.Source = reader.ReadToEnd();
123 }
124 this.SourceLength = this.Source.Length;
125 }
126
127 /// <summary>
128 /// Ctor.
129 /// </summary>
130 /// <param name="input">string containing source</param>
131 public JsonReader(string input)
132 {
133 this.Source = input;
134 this.SourceLength = this.Source.Length;
135 }
136
137 /// <summary>
138 /// Ctor.
139 /// </summary>
140 /// <param name="input">StringBuilder containing source</param>
141 public JsonReader(StringBuilder input)
142 {
143 this.Source = input.ToString();
144 this.SourceLength = this.Source.Length;
145 }
146
147 #endregion Init
148
149 #region Properties
150
151 /// <summary>
152 /// Gets and sets if ValueTypes can accept values of null
153 /// </summary>
154 /// <remarks>
155 /// Only affects deserialization: if a ValueType is assigned the
156 /// value of null, it will receive the value default(TheType).
157 /// Setting this to false, throws an exception if null is
158 /// specified for a ValueType member.
159 /// </remarks>
160 public bool AllowNullValueTypes
161 {
162 get { return this.Coercion.AllowNullValueTypes; }
163 set { this.Coercion.AllowNullValueTypes = value; }
164 }
165
166 /// <summary>
167 /// Gets and sets the property name used for type hinting.
168 /// </summary>
169 public string TypeHintName
170 {
171 get { return this.typeHintName; }
172 set { this.typeHintName = value; }
173 }
174
175 #endregion Properties
176
177 #region Parsing Methods
178
179 /// <summary>
180 /// Convert from JSON string to Object graph
181 /// </summary>
182 /// <returns></returns>
183 public object Deserialize()
184 {
185 return this.Deserialize((Type)null);
186 }
187
188 /// <summary>
189 /// Convert from JSON string to Object graph
190 /// </summary>
191 /// <returns></returns>
192 public object Deserialize(int start)
193 {
194 this.index = start;
195 return this.Deserialize((Type)null);
196 }
197
198 /// <summary>
199 /// Convert from JSON string to Object graph of specific Type
200 /// </summary>
201 /// <param name="type"></param>
202 /// <returns></returns>
203 public object Deserialize(Type type)
204 {
205 // should this run through a preliminary test here?
206 return this.Read(type, false);
207 }
208
209 /// <summary>
210 /// Convert from JSON string to Object graph of specific Type
211 /// </summary>
212 /// <param name="type"></param>
213 /// <returns></returns>
214 public T Deserialize<T>()
215 {
216 // should this run through a preliminary test here?
217 return (T)this.Read(typeof(T), false);
218 }
219
220 /// <summary>
221 /// Convert from JSON string to Object graph of specific Type
222 /// </summary>
223 /// <param name="type"></param>
224 /// <returns></returns>
225 public object Deserialize(int start, Type type)
226 {
227 this.index = start;
228
229 // should this run through a preliminary test here?
230 return this.Read(type, false);
231 }
232
233 /// <summary>
234 /// Convert from JSON string to Object graph of specific Type
235 /// </summary>
236 /// <param name="type"></param>
237 /// <returns></returns>
238 public T Deserialize<T>(int start)
239 {
240 this.index = start;
241
242 // should this run through a preliminary test here?
243 return (T)this.Read(typeof(T), false);
244 }
245
246 private object Read(Type expectedType, bool typeIsHint)
247 {
248 if (expectedType == typeof(Object))
249 {
250 expectedType = null;
251 }
252
253 JsonToken token = this.Tokenize();
254
255 switch (token)
256 {
257 case JsonToken.ObjectStart:
258 {
259 return this.ReadObject(typeIsHint ? null : expectedType);
260 }
261 case JsonToken.ArrayStart:
262 {
263 return this.ReadArray(typeIsHint ? null : expectedType);
264 }
265 case JsonToken.String:
266 {
267 return this.ReadString(typeIsHint ? null : expectedType);
268 }
269 case JsonToken.Number:
270 {
271 return this.ReadNumber(typeIsHint ? null : expectedType);
272 }
273 case JsonToken.False:
274 {
275 this.index += JsonReader.LiteralFalse.Length;
276 return false;
277 }
278 case JsonToken.True:
279 {
280 this.index += JsonReader.LiteralTrue.Length;
281 return true;
282 }
283 case JsonToken.Null:
284 {
285 this.index += JsonReader.LiteralNull.Length;
286 return null;
287 }
288 case JsonToken.NaN:
289 {
290 this.index += JsonReader.LiteralNotANumber.Length;
291 return Double.NaN;
292 }
293 case JsonToken.PositiveInfinity:
294 {
295 this.index += JsonReader.LiteralPositiveInfinity.Length;
296 return Double.PositiveInfinity;
297 }
298 case JsonToken.NegativeInfinity:
299 {
300 this.index += JsonReader.LiteralNegativeInfinity.Length;
301 return Double.NegativeInfinity;
302 }
303 case JsonToken.Undefined:
304 {
305 this.index += JsonReader.LiteralUndefined.Length;
306 return null;
307 }
308 case JsonToken.End:
309 default:
310 {
311 return null;
312 }
313 }
314 }
315
316 private object ReadObject(Type objectType)
317 {
318 if (this.Source[this.index] != JsonReader.OperatorObjectStart)
319 {
320 throw new JsonDeserializationException(JsonReader.ErrorExpectedObject, this.index);
321 }
322
323 Type genericDictionaryType = null;
324 Dictionary<string, MemberInfo> memberMap = null;
325 Object result;
326 if (objectType != null)
327 {
328 result = this.Coercion.InstantiateObject(objectType, out memberMap);
329
330 if (memberMap == null)
331 {
332 // this allows specific IDictionary<string, T> to deserialize T
333 Type genericDictionary = objectType.GetInterface(JsonReader.TypeGenericIDictionary);
334 if (genericDictionary != null)
335 {
336 Type[] genericArgs = genericDictionary.GetGenericArguments();
337 if (genericArgs.Length == 2)
338 {
339 if (genericArgs[0] != typeof(String))
340 {
341 throw new JsonDeserializationException(
342 String.Format(JsonReader.ErrorGenericIDictionaryKeys, objectType),
343 this.index);
344 }
345
346 if (genericArgs[1] != typeof(Object))
347 {
348 genericDictionaryType = genericArgs[1];
349 }
350 }
351 }
352 }
353 }
354 else
355 {
356 result = new Dictionary<String, Object>();
357 }
358
359 JsonToken token;
360 do
361 {
362 Type memberType;
363 MemberInfo memberInfo;
364
365 // consume opening brace or delim
366 this.index++;
367 if (this.index >= this.SourceLength)
368 {
369 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedObject, this.index);
370 }
371
372 // get next token
373 token = this.Tokenize();
374 if (token == JsonToken.ObjectEnd)
375 {
376 break;
377 }
378
379 if (token != JsonToken.String)
380 {
381 throw new JsonDeserializationException(JsonReader.ErrorExpectedPropertyName, this.index);
382 }
383
384 // parse object member value
385 string memberName = (String)this.ReadString(null);
386
387 if (genericDictionaryType == null && memberMap != null)
388 {
389 // determine the type of the property/field
390 memberType = TypeCoercionUtility.GetMemberInfo(memberMap, memberName, out memberInfo);
391 }
392 else
393 {
394 memberType = genericDictionaryType;
395 memberInfo = null;
396 }
397
398 // get next token
399 token = this.Tokenize();
400 if (token != JsonToken.NameDelim)
401 {
402 throw new JsonDeserializationException(JsonReader.ErrorExpectedPropertyNameDelim, this.index);
403 }
404
405 // consume delim
406 this.index++;
407 if (this.index >= this.SourceLength)
408 {
409 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedObject, this.index);
410 }
411
412 // parse object member value
413 object value = this.Read(memberType, false);
414
415 if (result is IDictionary)
416 {
417 if (objectType == null &&
418 !String.IsNullOrEmpty(this.TypeHintName) &&
419 this.TypeHintName.Equals(memberName, StringComparison.InvariantCulture))
420 {
421 result = this.Coercion.ProcessTypeHint((IDictionary)result, value as string, out objectType, out memberMap);
422 }
423 else
424 {
425 ((IDictionary)result)[memberName] = value;
426 }
427 }
428 else if (objectType.GetInterface(JsonReader.TypeGenericIDictionary) != null)
429 {
430 throw new JsonDeserializationException(
431 String.Format(JsonReader.ErrorGenericIDictionary, objectType),
432 this.index);
433 }
434 else
435 {
436 this.Coercion.SetMemberValue(result, memberType, memberInfo, value);
437 }
438
439 // get next token
440 token = this.Tokenize();
441 } while (token == JsonToken.ValueDelim);
442
443 if (token != JsonToken.ObjectEnd)
444 {
445 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedObject, this.index);
446 }
447
448 // consume closing brace
449 this.index++;
450
451 return result;
452 }
453
454 private IEnumerable ReadArray(Type arrayType)
455 {
456 if (this.Source[this.index] != JsonReader.OperatorArrayStart)
457 {
458 throw new JsonDeserializationException(JsonReader.ErrorExpectedArray, this.index);
459 }
460
461 bool isArrayItemTypeSet = (arrayType != null);
462 bool isArrayTypeAHint = !isArrayItemTypeSet;
463 Type arrayItemType = null;
464
465 if (isArrayItemTypeSet)
466 {
467 if (arrayType.HasElementType)
468 {
469 arrayItemType = arrayType.GetElementType();
470 }
471 else if (arrayType.IsGenericType)
472 {
473 Type[] generics = arrayType.GetGenericArguments();
474 if (generics.Length == 1)
475 {
476 // could use the first or last, but this more correct
477 arrayItemType = generics[0];
478 }
479 }
480 }
481
482 // using ArrayList since has .ToArray(Type) method
483 // cannot create generic list at runtime
484 ArrayList jsArray = new ArrayList();
485
486 JsonToken token;
487 do
488 {
489 // consume opening bracket or delim
490 this.index++;
491 if (this.index >= this.SourceLength)
492 {
493 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedArray, this.index);
494 }
495
496 // get next token
497 token = this.Tokenize();
498 if (token == JsonToken.ArrayEnd)
499 {
500 break;
501 }
502
503 // parse array item
504 object value = this.Read(arrayItemType, isArrayTypeAHint);
505 jsArray.Add(value);
506
507 // establish if array is of common type
508 if (value == null)
509 {
510 if (arrayItemType != null && arrayItemType.IsValueType)
511 {
512 // use plain object to hold null
513 arrayItemType = null;
514 }
515 isArrayItemTypeSet = true;
516 }
517 else if (arrayItemType != null && !arrayItemType.IsAssignableFrom(value.GetType()))
518 {
519 if (value.GetType().IsAssignableFrom(arrayItemType))
520 {
521 // attempt to use the more general type
522 arrayItemType = value.GetType();
523 }
524 else
525 {
526 // use plain object to hold value
527 arrayItemType = null;
528 isArrayItemTypeSet = true;
529 }
530 }
531 else if (!isArrayItemTypeSet)
532 {
533 // try out a hint type
534 // if hasn't been set before
535 arrayItemType = value.GetType();
536 isArrayItemTypeSet = true;
537 }
538
539 // get next token
540 token = this.Tokenize();
541 } while (token == JsonToken.ValueDelim);
542
543 if (token != JsonToken.ArrayEnd)
544 {
545 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedArray, this.index);
546 }
547
548 // consume closing bracket
549 this.index++;
550
551 // TODO: optimize to reduce number of conversions on lists
552
553 if (arrayItemType != null && arrayItemType != typeof(object))
554 {
555 // if all items are of same type then convert to array of that type
556 return jsArray.ToArray(arrayItemType);
557 }
558
559 // convert to an object array for consistency
560 return jsArray.ToArray();
561 }
562
563 /// <summary>
564 /// Reads a JSON string
565 /// </summary>
566 /// <param name="expectedType"></param>
567 /// <returns>string or value which is represented as a string in JSON</returns>
568 private object ReadString(Type expectedType)
569 {
570 if (this.Source[this.index] != JsonReader.OperatorStringDelim &&
571 this.Source[this.index] != JsonReader.OperatorStringDelimAlt)
572 {
573 throw new JsonDeserializationException(JsonReader.ErrorExpectedString, this.index);
574 }
575
576 char startStringDelim = this.Source[this.index];
577
578 // consume opening quote
579 this.index++;
580 if (this.index >= this.SourceLength)
581 {
582 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedString, this.index);
583 }
584
585 int start = this.index;
586 StringBuilder builder = new StringBuilder();
587
588 while (this.Source[this.index] != startStringDelim)
589 {
590 if (this.Source[this.index] == JsonReader.OperatorCharEscape)
591 {
592 // copy chunk before decoding
593 builder.Append(this.Source, start, this.index - start);
594
595 // consume escape char
596 this.index++;
597 if (this.index >= this.SourceLength)
598 {
599 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedString, this.index);
600 }
601
602 // decode
603 switch (this.Source[this.index])
604 {
605 case '0':
606 {
607 // don't allow NULL char '\0'
608 // causes CStrings to terminate
609 break;
610 }
611 case 'b':
612 {
613 // backspace
614 builder.Append('\b');
615 break;
616 }
617 case 'f':
618 {
619 // formfeed
620 builder.Append('\f');
621 break;
622 }
623 case 'n':
624 {
625 // newline
626 builder.Append('\n');
627 break;
628 }
629 case 'r':
630 {
631 // carriage return
632 builder.Append('\r');
633 break;
634 }
635 case 't':
636 {
637 // tab
638 builder.Append('\t');
639 break;
640 }
641 case 'u':
642 {
643 // Unicode escape sequence
644 // e.g. Copyright: "\u00A9"
645
646 // unicode ordinal
647 int utf16;
648 if (this.index+4 < this.SourceLength &&
649 Int32.TryParse(
650 this.Source.Substring(this.index+1, 4),
651 NumberStyles.AllowHexSpecifier,
652 NumberFormatInfo.InvariantInfo,
653 out utf16))
654 {
655 builder.Append(Char.ConvertFromUtf32(utf16));
656 this.index += 4;
657 }
658 else
659 {
660 // using FireFox style recovery, if not a valid hex
661 // escape sequence then treat as single escaped 'u'
662 // followed by rest of string
663 builder.Append(this.Source[this.index]);
664 }
665 break;
666 }
667 default:
668 {
669 builder.Append(this.Source[this.index]);
670 break;
671 }
672 }
673
674 this.index++;
675 if (this.index >= this.SourceLength)
676 {
677 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedString, this.index);
678 }
679
680 start = this.index;
681 }
682 else
683 {
684 // next char
685 this.index++;
686 if (this.index >= this.SourceLength)
687 {
688 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedString, this.index);
689 }
690 }
691 }
692
693 // copy rest of string
694 builder.Append(this.Source, start, this.index-start);
695
696 // consume closing quote
697 this.index++;
698
699 if (expectedType != null && expectedType != typeof(String))
700 {
701 return this.Coercion.CoerceType(expectedType, builder.ToString());
702 }
703
704 return builder.ToString();
705 }
706
707 private object ReadNumber(Type expectedType)
708 {
709 bool hasDecimal = false;
710 bool hasExponent = false;
711 int start = this.index;
712 int precision = 0;
713 int exponent = 0;
714
715 // optional minus part
716 if (this.Source[this.index] == JsonReader.OperatorNegate)
717 {
718 // consume sign
719 this.index++;
720 if (this.index >= this.SourceLength || !Char.IsDigit(this.Source[this.index]))
721 throw new JsonDeserializationException(JsonReader.ErrorIllegalNumber, this.index);
722 }
723
724 // integer part
725 while ((this.index < this.SourceLength) && Char.IsDigit(this.Source[this.index]))
726 {
727 // consume digit
728 this.index++;
729 }
730
731 // optional decimal part
732 if ((this.index < this.SourceLength) && (this.Source[this.index] == '.'))
733 {
734 hasDecimal = true;
735
736 // consume decimal
737 this.index++;
738 if (this.index >= this.SourceLength || !Char.IsDigit(this.Source[this.index]))
739 {
740 throw new JsonDeserializationException(JsonReader.ErrorIllegalNumber, this.index);
741 }
742
743 // fraction part
744 while (this.index < this.SourceLength && Char.IsDigit(this.Source[this.index]))
745 {
746 // consume digit
747 this.index++;
748 }
749 }
750
751 // note the number of significant digits
752 precision = this.index-start - (hasDecimal ? 1 : 0);
753
754 // optional exponent part
755 if (this.index < this.SourceLength && (this.Source[this.index] == 'e' || this.Source[this.index] == 'E'))
756 {
757 hasExponent = true;
758
759 // consume 'e'
760 this.index++;
761 if (this.index >= this.SourceLength)
762 {
763 throw new JsonDeserializationException(JsonReader.ErrorIllegalNumber, this.index);
764 }
765
766 int expStart = this.index;
767
768 // optional minus/plus part
769 if (this.Source[this.index] == JsonReader.OperatorNegate || this.Source[this.index] == JsonReader.OperatorUnaryPlus)
770 {
771 // consume sign
772 this.index++;
773 if (this.index >= this.SourceLength || !Char.IsDigit(this.Source[this.index]))
774 {
775 throw new JsonDeserializationException(JsonReader.ErrorIllegalNumber, this.index);
776 }
777 }
778 else
779 {
780 if (!Char.IsDigit(this.Source[this.index]))
781 {
782 throw new JsonDeserializationException(JsonReader.ErrorIllegalNumber, this.index);
783 }
784 }
785
786 // exp part
787 while (this.index < this.SourceLength && Char.IsDigit(this.Source[this.index]))
788 {
789 // consume digit
790 this.index++;
791 }
792
793 Int32.TryParse(this.Source.Substring(expStart, this.index-expStart), NumberStyles.Integer,
794 NumberFormatInfo.InvariantInfo, out exponent);
795 }
796
797 // at this point, we have the full number string and know its characteristics
798 string numberString = this.Source.Substring(start, this.index - start);
799
800 if (!hasDecimal && !hasExponent && precision < 19)
801 {
802 // is Integer value
803
804 // parse as most flexible
805 decimal number = Decimal.Parse(
806 numberString,
807 NumberStyles.Integer,
808 NumberFormatInfo.InvariantInfo);
809
810 if (expectedType != null)
811 {
812 return this.Coercion.CoerceType(expectedType, number);
813 }
814
815 if (number >= Int32.MinValue && number <= Int32.MaxValue)
816 {
817 // use most common
818 return (int)number;
819 }
820 if (number >= Int64.MinValue && number <= Int64.MaxValue)
821 {
822 // use more flexible
823 return (long)number;
824 }
825
826 // use most flexible
827 return number;
828 }
829 else
830 {
831 // is Floating Point value
832
833 if (expectedType == typeof(Decimal))
834 {
835 // special case since Double does not convert to Decimal
836 return Decimal.Parse(
837 numberString,
838 NumberStyles.Float,
839 NumberFormatInfo.InvariantInfo);
840 }
841
842 // use native EcmaScript number (IEEE 754)
843 double number = Double.Parse(
844 numberString,
845 NumberStyles.Float,
846 NumberFormatInfo.InvariantInfo);
847
848 if (expectedType != null)
849 {
850 return this.Coercion.CoerceType(expectedType, number);
851 }
852
853 return number;
854 }
855 }
856
857 #endregion Parsing Methods
858
859 #region Static Methods
860
861 /// <summary>
862 /// A fast method for deserializing an object from JSON
863 /// </summary>
864 /// <param name="value"></param>
865 /// <returns></returns>
866 public static object Deserialize(string value)
867 {
868 return JsonReader.Deserialize(value, 0, null);
869 }
870
871 /// <summary>
872 /// A fast method for deserializing an object from JSON
873 /// </summary>
874 /// <typeparam name="T"></typeparam>
875 /// <param name="value"></param>
876 /// <returns></returns>
877 public static T Deserialize<T>(string value)
878 {
879 return (T)JsonReader.Deserialize(value, 0, typeof(T));
880 }
881
882 /// <summary>
883 /// A fast method for deserializing an object from JSON
884 /// </summary>
885 /// <param name="value"></param>
886 /// <param name="start"></param>
887 /// <returns></returns>
888 public static object Deserialize(string value, int start)
889 {
890 return JsonReader.Deserialize(value, start, null);
891 }
892
893 /// <summary>
894 /// A fast method for deserializing an object from JSON
895 /// </summary>
896 /// <typeparam name="T"></typeparam>
897 /// <param name="value"></param>
898 /// <param name="start"></param>
899 /// <returns></returns>
900 public static T Deserialize<T>(string value, int start)
901 {
902 return (T)JsonReader.Deserialize(value, start, typeof(T));
903 }
904
905 /// <summary>
906 /// A fast method for deserializing an object from JSON
907 /// </summary>
908 /// <param name="value"></param>
909 /// <param name="type"></param>
910 /// <returns></returns>
911 public static object Deserialize(string value, Type type)
912 {
913 return JsonReader.Deserialize(value, 0, type);
914 }
915
916 /// <summary>
917 /// A fast method for deserializing an object from JSON
918 /// </summary>
919 /// <param name="value">source text</param>
920 /// <param name="start">starting position</param>
921 /// <param name="type">expected type</param>
922 /// <returns></returns>
923 public static object Deserialize(string value, int start, Type type)
924 {
925 return (new JsonReader(value)).Deserialize(start, type);
926 }
927
928 #endregion Static Methods
929
930 #region Tokenizing Methods
931
932 private JsonToken Tokenize()
933 {
934 if (this.index >= this.SourceLength)
935 {
936 return JsonToken.End;
937 }
938
939 // skip whitespace
940 while (Char.IsWhiteSpace(this.Source[this.index]))
941 {
942 this.index++;
943 if (this.index >= this.SourceLength)
944 {
945 return JsonToken.End;
946 }
947 }
948
949 #region Skip Comments
950
951 // skip block and line comments
952 if (this.Source[this.index] == JsonReader.CommentStart[0])
953 {
954 if (this.index+1 >= this.SourceLength)
955 {
956 throw new JsonDeserializationException(JsonReader.ErrorUnrecognizedToken, this.index);
957 }
958
959 // skip over first char of comment start
960 this.index++;
961
962 bool isBlockComment = false;
963 if (this.Source[this.index] == JsonReader.CommentStart[1])
964 {
965 isBlockComment = true;
966 }
967 else if (this.Source[this.index] != JsonReader.CommentLine[1])
968 {
969 throw new JsonDeserializationException(JsonReader.ErrorUnrecognizedToken, this.index);
970 }
971 // skip over second char of comment start
972 this.index++;
973
974 if (isBlockComment)
975 {
976 // store index for unterminated case
977 int commentStart = this.index-2;
978
979 if (this.index+1 >= this.SourceLength)
980 {
981 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedComment, commentStart);
982 }
983
984 // skip over everything until reach block comment ending
985 while (this.Source[this.index] != JsonReader.CommentEnd[0] ||
986 this.Source[this.index+1] != JsonReader.CommentEnd[1])
987 {
988 this.index++;
989 if (this.index+1 >= this.SourceLength)
990 {
991 throw new JsonDeserializationException(JsonReader.ErrorUnterminatedComment, commentStart);
992 }
993 }
994
995 // skip block comment end token
996 this.index += 2;
997 if (this.index >= this.SourceLength)
998 {
999 return JsonToken.End;
1000 }
1001 }
1002 else
1003 {
1004 // skip over everything until reach line ending
1005 while (JsonReader.LineEndings.IndexOf(this.Source[this.index]) < 0)
1006 {
1007 this.index++;
1008 if (this.index >= this.SourceLength)
1009 {
1010 return JsonToken.End;
1011 }
1012 }
1013 }
1014
1015 // skip whitespace again
1016 while (Char.IsWhiteSpace(this.Source[this.index]))
1017 {
1018 this.index++;
1019 if (this.index >= this.SourceLength)
1020 {
1021 return JsonToken.End;
1022 }
1023 }
1024 }
1025
1026 #endregion Skip Comments
1027
1028 // consume positive signing (as is extraneous)
1029 if (this.Source[this.index] == JsonReader.OperatorUnaryPlus)
1030 {
1031 this.index++;
1032 if (this.index >= this.SourceLength)
1033 {
1034 return JsonToken.End;
1035 }
1036 }
1037
1038 switch (this.Source[this.index])
1039 {
1040 case JsonReader.OperatorArrayStart:
1041 {
1042 return JsonToken.ArrayStart;
1043 }
1044 case JsonReader.OperatorArrayEnd:
1045 {
1046 return JsonToken.ArrayEnd;
1047 }
1048 case JsonReader.OperatorObjectStart:
1049 {
1050 return JsonToken.ObjectStart;
1051 }
1052 case JsonReader.OperatorObjectEnd:
1053 {
1054 return JsonToken.ObjectEnd;
1055 }
1056 case JsonReader.OperatorStringDelim:
1057 case JsonReader.OperatorStringDelimAlt:
1058 {
1059 return JsonToken.String;
1060 }
1061 case JsonReader.OperatorValueDelim:
1062 {
1063 return JsonToken.ValueDelim;
1064 }
1065 case JsonReader.OperatorNameDelim:
1066 {
1067 return JsonToken.NameDelim;
1068 }
1069 default:
1070 {
1071 break;
1072 }
1073 }
1074
1075 // number
1076 if (Char.IsDigit(this.Source[this.index]) ||
1077 ((this.Source[this.index] == JsonReader.OperatorNegate) && (this.index+1 < this.SourceLength) && Char.IsDigit(this.Source[this.index+1])))
1078 {
1079 return JsonToken.Number;
1080 }
1081
1082 // "false" literal
1083 if (this.MatchLiteral(JsonReader.LiteralFalse))
1084 {
1085 return JsonToken.False;
1086 }
1087
1088 // "true" literal
1089 if (this.MatchLiteral(JsonReader.LiteralTrue))
1090 {
1091 return JsonToken.True;
1092 }
1093
1094 // "null" literal
1095 if (this.MatchLiteral(JsonReader.LiteralNull))
1096 {
1097 return JsonToken.Null;
1098 }
1099
1100 // "NaN" literal
1101 if (this.MatchLiteral(JsonReader.LiteralNotANumber))
1102 {
1103 return JsonToken.NaN;
1104 }
1105
1106 // "Infinity" literal
1107 if (this.MatchLiteral(JsonReader.LiteralPositiveInfinity))
1108 {
1109 return JsonToken.PositiveInfinity;
1110 }
1111
1112 // "-Infinity" literal
1113 if (this.MatchLiteral(JsonReader.LiteralNegativeInfinity))
1114 {
1115 return JsonToken.NegativeInfinity;
1116 }
1117
1118 // "undefined" literal
1119 if (this.MatchLiteral(JsonReader.LiteralUndefined))
1120 {
1121 return JsonToken.Undefined;
1122 }
1123
1124 throw new JsonDeserializationException(JsonReader.ErrorUnrecognizedToken, this.index);
1125 }
1126
1127 /// <summary>
1128 /// Determines if the next token is the given literal
1129 /// </summary>
1130 /// <param name="literal"></param>
1131 /// <returns></returns>
1132 private bool MatchLiteral(string literal)
1133 {
1134 int literalLength = literal.Length;
1135 for (int i=0, j=this.index; i<literalLength && j<this.SourceLength; i++, j++)
1136 {
1137 if (literal[i] != this.Source[j])
1138 {
1139 return false;
1140 }
1141 }
1142
1143 return true;
1144 }
1145
1146 #endregion Tokenizing Methods
1147
1148 #region Type Methods
1149
1150 /// <summary>
1151 /// Converts a value into the specified type using type inference.
1152 /// </summary>
1153 /// <typeparam name="T">target type</typeparam>
1154 /// <param name="value">value to convert</param>
1155 /// <param name="typeToMatch">example object to get the type from</param>
1156 /// <returns></returns>
1157 public static T CoerceType<T>(object value, T typeToMatch)
1158 {
1159 return (T)new TypeCoercionUtility().CoerceType(typeof(T), value);
1160 }
1161
1162 /// <summary>
1163 /// Converts a value into the specified type.
1164 /// </summary>
1165 /// <typeparam name="T">target type</typeparam>
1166 /// <param name="value">value to convert</param>
1167 /// <returns></returns>
1168 public static T CoerceType<T>(object value)
1169 {
1170 return (T)new TypeCoercionUtility().CoerceType(typeof(T), value);
1171 }
1172
1173 /// <summary>
1174 /// Converts a value into the specified type.
1175 /// </summary>
1176 /// <param name="targetType">target type</param>
1177 /// <param name="value">value to convert</param>
1178 /// <returns></returns>
1179 public static object CoerceType(Type targetType, object value)
1180 {
1181 return new TypeCoercionUtility().CoerceType(targetType, value);
1182 }
1183
1184 #endregion Type Methods
1185
1186 #region TypeCoercionUtility
1187
1188 /// <summary>
1189 /// Utility for forcing conversion between types
1190 /// </summary>
1191 private class TypeCoercionUtility
1192 {
1193 #region Constants
1194
1195 private const string ErrorNullValueType = "{0} does not accept null as a value";
1196 private const string ErrorDefaultCtor = "Only objects with default constructors can be deserialized. ({0})";
1197 private const string ErrorCannotInstantiate = "Interfaces, Abstract classes, and unsupported ValueTypes cannot be deserialized. ({0})";
1198
1199 #endregion Constants
1200
1201 #region Fields
1202
1203 private bool allowNullValueTypes;
1204 private Dictionary<Type, Dictionary<string, MemberInfo>> memberMapCache;
1205
1206 #endregion Fields
1207
1208 #region Properties
1209
1210 private Dictionary<Type, Dictionary<string, MemberInfo>> MemberMapCache
1211 {
1212 get
1213 {
1214 if (this.memberMapCache == null)
1215 {
1216 // instantiate space for cache
1217 this.memberMapCache = new Dictionary<Type, Dictionary<string, MemberInfo>>();
1218 }
1219 return this.memberMapCache;
1220 }
1221 }
1222
1223 /// <summary>
1224 /// Gets and sets if ValueTypes can accept values of null
1225 /// </summary>
1226 /// <remarks>
1227 /// Only affects deserialization: if a ValueType is assigned the
1228 /// value of null, it will receive the value default(TheType).
1229 /// Setting this to false, throws an exception if null is
1230 /// specified for a ValueType member.
1231 /// </remarks>
1232 public bool AllowNullValueTypes
1233 {
1234 get { return this.allowNullValueTypes; }
1235 set { this.allowNullValueTypes = value; }
1236 }
1237
1238 #endregion Properties
1239
1240 #region Object Methods
1241
1242 /// <summary>
1243 /// If a Type Hint is present then this method attempts to
1244 /// use it and move any previously parsed data over.
1245 /// </summary>
1246 /// <param name="result">the previous result</param>
1247 /// <param name="typeInfo">the type info string to use</param>
1248 /// <param name="objectType">reference to the objectType</param>
1249 /// <param name="memberMap">reference to the memberMap</param>
1250 /// <returns></returns>
1251 internal object ProcessTypeHint(
1252 IDictionary result,
1253 string typeInfo,
1254 out Type objectType,
1255 out Dictionary<string, MemberInfo> memberMap)
1256 {
1257 if (String.IsNullOrEmpty(typeInfo))
1258 {
1259 objectType = null;
1260 memberMap = null;
1261 return result;
1262 }
1263
1264 Type hintedType = Type.GetType(typeInfo, false);
1265 if (hintedType == null)
1266 {
1267 objectType = null;
1268 memberMap = null;
1269 return result;
1270 }
1271
1272 objectType = hintedType;
1273 return this.CoerceType(hintedType, result, out memberMap);
1274 }
1275
1276 internal Object InstantiateObject(Type objectType, out Dictionary<string, MemberInfo> memberMap)
1277 {
1278 if (objectType.IsInterface || objectType.IsAbstract || objectType.IsValueType)
1279 {
1280 throw new JsonTypeCoersionException(
1281 String.Format(TypeCoercionUtility.ErrorCannotInstantiate, objectType.FullName));
1282 }
1283
1284 ConstructorInfo ctor = objectType.GetConstructor(Type.EmptyTypes);
1285 if (ctor == null)
1286 {
1287 throw new JsonTypeCoersionException(
1288 String.Format(TypeCoercionUtility.ErrorDefaultCtor, objectType.FullName));
1289 }
1290 Object result;
1291 try
1292 {
1293 // always try-catch Invoke() to expose real exception
1294 result = ctor.Invoke(null);
1295 }
1296 catch (TargetInvocationException ex)
1297 {
1298 if (ex.InnerException != null)
1299 {
1300 throw new JsonTypeCoersionException(ex.InnerException.Message, ex.InnerException);
1301 }
1302 throw new JsonTypeCoersionException("Error instantiating " + objectType.FullName, ex);
1303 }
1304
1305 // don't incurr the cost of member map for dictionaries
1306 if (typeof(IDictionary).IsAssignableFrom(objectType))
1307 {
1308 memberMap = null;
1309 }
1310 else
1311 {
1312 memberMap = this.CreateMemberMap(objectType);
1313 }
1314 return result;
1315 }
1316
1317 private Dictionary<string, MemberInfo> CreateMemberMap(Type objectType)
1318 {
1319 if (this.MemberMapCache.ContainsKey(objectType))
1320 {
1321 // map was stored in cache
1322 return this.MemberMapCache[objectType];
1323 }
1324
1325 // create a new map
1326 Dictionary<string, MemberInfo> memberMap = new Dictionary<string, MemberInfo>();
1327
1328 // load properties into property map
1329 PropertyInfo[] properties = objectType.GetProperties();
1330 foreach (PropertyInfo info in properties)
1331 {
1332 if (!info.CanRead || !info.CanWrite)
1333 {
1334 continue;
1335 }
1336
1337 if (JsonIgnoreAttribute.IsJsonIgnore(info))
1338 {
1339 continue;
1340 }
1341
1342 string jsonName = JsonNameAttribute.GetJsonName(info);
1343 if (String.IsNullOrEmpty(jsonName))
1344 {
1345 memberMap[info.Name] = info;
1346 }
1347 else
1348 {
1349 memberMap[jsonName] = info;
1350 }
1351 }
1352
1353 // load public fields into property map
1354 FieldInfo[] fields = objectType.GetFields();
1355 foreach (FieldInfo info in fields)
1356 {
1357 if (!info.IsPublic)
1358 {
1359 continue;
1360 }
1361
1362 if (JsonIgnoreAttribute.IsJsonIgnore(info))
1363 {
1364 continue;
1365 }
1366
1367 string jsonName = JsonNameAttribute.GetJsonName(info);
1368 if (String.IsNullOrEmpty(jsonName))
1369 {
1370 memberMap[info.Name] = info;
1371 }
1372 else
1373 {
1374 memberMap[jsonName] = info;
1375 }
1376 }
1377
1378 // store in cache for repeated usage
1379 this.MemberMapCache[objectType] = memberMap;
1380
1381 return memberMap;
1382 }
1383
1384 internal static Type GetMemberInfo(
1385 Dictionary<string, MemberInfo> memberMap,
1386 string memberName,
1387 out MemberInfo memberInfo)
1388 {
1389 if (memberMap != null &&
1390 memberMap.ContainsKey(memberName))
1391 {
1392 // Check properties for object member
1393 memberInfo = memberMap[memberName];
1394
1395 if (memberInfo is PropertyInfo)
1396 {
1397 // maps to public property
1398 return ((PropertyInfo)memberInfo).PropertyType;
1399 }
1400 else if (memberInfo is FieldInfo)
1401 {
1402 // maps to public field
1403 return ((FieldInfo)memberInfo).FieldType;
1404 }
1405 }
1406
1407 memberInfo = null;
1408 return null;
1409 }
1410
1411 /// <summary>
1412 /// Helper method to set value of either property or field
1413 /// </summary>
1414 /// <param name="result"></param>
1415 /// <param name="memberType"></param>
1416 /// <param name="memberInfo"></param>
1417 /// <param name="value"></param>
1418 internal void SetMemberValue(Object result, Type memberType, MemberInfo memberInfo, object value)
1419 {
1420 if (memberInfo is PropertyInfo)
1421 {
1422 // set value of public property
1423 ((PropertyInfo)memberInfo).SetValue(
1424 result,
1425 this.CoerceType(memberType, value),
1426 null);
1427 }
1428 else if (memberInfo is FieldInfo)
1429 {
1430 // set value of public field
1431 ((FieldInfo)memberInfo).SetValue(
1432 result,
1433 this.CoerceType(memberType, value));
1434 }
1435
1436 // all other values are ignored
1437 }
1438
1439 #endregion Object Methods
1440
1441 #region Type Methods
1442
1443 internal object CoerceType(Type targetType, object value)
1444 {
1445 bool isNullable = TypeCoercionUtility.IsNullable(targetType);
1446 if (value == null)
1447 {
1448 if (this.allowNullValueTypes &&
1449 targetType.IsValueType &&
1450 !isNullable)
1451 {
1452 throw new JsonTypeCoersionException(String.Format(TypeCoercionUtility.ErrorNullValueType, targetType.FullName));
1453 }
1454 return value;
1455 }
1456
1457 if (isNullable)
1458 {
1459 // nullable types have a real underlying struct
1460 Type[] genericArgs = targetType.GetGenericArguments();
1461 if (genericArgs.Length == 1)
1462 {
1463 targetType = genericArgs[0];
1464 }
1465 }
1466
1467 Type actualType = value.GetType();
1468 if (targetType.IsAssignableFrom(actualType))
1469 {
1470 return value;
1471 }
1472
1473 if (targetType.IsEnum)
1474 {
1475 if (value is String)
1476 {
1477 if (!Enum.IsDefined(targetType, value))
1478 {
1479 // if isn't a defined value perhaps it is the JsonName
1480 foreach (FieldInfo field in targetType.GetFields())
1481 {
1482 string jsonName = JsonNameAttribute.GetJsonName(field);
1483 if (((string)value).Equals(jsonName))
1484 {
1485 value = field.Name;
1486 break;
1487 }
1488 }
1489 }
1490
1491 return Enum.Parse(targetType, (string)value);
1492 }
1493 else
1494 {
1495 value = this.CoerceType(Enum.GetUnderlyingType(targetType), value);
1496 return Enum.ToObject(targetType, value);
1497 }
1498 }
1499
1500 if (value is IDictionary)
1501 {
1502 Dictionary<string, MemberInfo> memberMap;
1503 return this.CoerceType(targetType, (IDictionary)value, out memberMap);
1504 }
1505
1506 if (typeof(IEnumerable).IsAssignableFrom(targetType) &&
1507 typeof(IEnumerable).IsAssignableFrom(actualType))
1508 {
1509 return this.CoerceArray(targetType, actualType, value);
1510 }
1511
1512 if (value is String)
1513 {
1514 if (targetType == typeof(DateTime))
1515 {
1516 DateTime date;
1517 if (DateTime.TryParse(
1518 (string)value,
1519 DateTimeFormatInfo.InvariantInfo,
1520 DateTimeStyles.RoundtripKind | DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.NoCurrentDateDefault,
1521 out date))
1522 {
1523 return date;
1524 }
1525 }
1526 else if (targetType == typeof(Guid))
1527 {
1528 // try-catch is pointless since will throw upon generic conversion
1529 return new Guid((string)value);
1530 }
1531 else if (targetType == typeof(Char))
1532 {
1533 if (((string)value).Length == 1)
1534 {
1535 return ((string)value)[0];
1536 }
1537 }
1538 else if (targetType == typeof(Uri))
1539 {
1540 Uri uri;
1541 if (Uri.TryCreate((string)value, UriKind.RelativeOrAbsolute, out uri))
1542 {
1543 return uri;
1544 }
1545 }
1546 else if (targetType == typeof(Version))
1547 {
1548 // try-catch is pointless since will throw upon generic conversion
1549 return new Version((string)value);
1550 }
1551 }
1552 else if (targetType == typeof(TimeSpan))
1553 {
1554 return new TimeSpan((long)this.CoerceType(typeof(Int64), value));
1555 }
1556
1557 TypeConverter converter = TypeDescriptor.GetConverter(targetType);
1558 if (converter.CanConvertFrom(actualType))
1559 {
1560 return converter.ConvertFrom(value);
1561 }
1562
1563 converter = TypeDescriptor.GetConverter(actualType);
1564 if (converter.CanConvertTo(targetType))
1565 {
1566 return converter.ConvertTo(value, targetType);
1567 }
1568
1569 try
1570 {
1571 // fall back to basics
1572 return Convert.ChangeType(value, targetType);
1573 }
1574 catch (Exception ex)
1575 {
1576 throw new JsonTypeCoersionException(
1577 String.Format("Error converting {0} to {1}", value.GetType().FullName, targetType.FullName), ex);
1578 }
1579 }
1580
1581 private object CoerceType(Type targetType, IDictionary value, out Dictionary<string, MemberInfo> memberMap)
1582 {
1583 object newValue = this.InstantiateObject(targetType, out memberMap);
1584 if (memberMap != null)
1585 {
1586 // copy any values into new object
1587 foreach (object key in value.Keys)
1588 {
1589 MemberInfo memberInfo;
1590 Type memberType = TypeCoercionUtility.GetMemberInfo(memberMap, key as String, out memberInfo);
1591 this.SetMemberValue(newValue, memberType, memberInfo, value[key]);
1592 }
1593 }
1594 return newValue;
1595 }
1596
1597 private object CoerceArray(Type targetType, Type arrayType, object value)
1598 {
1599 // targetType serializes as a JSON array but is not an array
1600 // assume is an ICollection / IEnumerable with AddRange, Add,
1601 // or custom Constructor with which we can populate it
1602
1603 // many ICollection types take an IEnumerable or ICollection
1604 // as a constructor argument. look through constructors for
1605 // a compatible match.
1606 ConstructorInfo[] ctors = targetType.GetConstructors();
1607 ConstructorInfo defaultCtor = null;
1608 foreach (ConstructorInfo ctor in ctors)
1609 {
1610 ParameterInfo[] paramList = ctor.GetParameters();
1611 if (paramList.Length == 0)
1612 {
1613 // save for in case cannot find closer match
1614 defaultCtor = ctor;
1615 continue;
1616 }
1617
1618 if (paramList.Length == 1 &&
1619 paramList[0].ParameterType.IsAssignableFrom(arrayType))
1620 {
1621 try
1622 {
1623 // invoke first constructor that can take this value as an argument
1624 return ctor.Invoke(
1625 new object[] { value }
1626 );
1627 }
1628 catch
1629 {
1630 // there might exist a better match
1631 continue;
1632 }
1633 }
1634 }
1635
1636 if (defaultCtor == null)
1637 {
1638 throw new JsonTypeCoersionException(
1639 String.Format(TypeCoercionUtility.ErrorDefaultCtor, targetType.FullName));
1640 }
1641 object collection;
1642 try
1643 {
1644 // always try-catch Invoke() to expose real exception
1645 collection = defaultCtor.Invoke(null);
1646 }
1647 catch (TargetInvocationException ex)
1648 {
1649 if (ex.InnerException != null)
1650 {
1651 throw new JsonTypeCoersionException(ex.InnerException.Message, ex.InnerException);
1652 }
1653 throw new JsonTypeCoersionException("Error instantiating " + targetType.FullName, ex);
1654 }
1655
1656 // many ICollection types have an AddRange method
1657 // which adds all items at once
1658 MethodInfo method = targetType.GetMethod("AddRange");
1659 ParameterInfo[] parameters = (method == null) ?
1660 null : method.GetParameters();
1661 Type paramType = (parameters == null || parameters.Length != 1) ?
1662 null : parameters[0].ParameterType;
1663 if (paramType != null &&
1664 paramType.IsAssignableFrom(arrayType))
1665 {
1666 try
1667 {
1668 // always try-catch Invoke() to expose real exception
1669 // add all members in one method
1670 method.Invoke(
1671 collection,
1672 new object[] { value });
1673 }
1674 catch (TargetInvocationException ex)
1675 {
1676 if (ex.InnerException != null)
1677 {
1678 throw new JsonTypeCoersionException(ex.InnerException.Message, ex.InnerException);
1679 }
1680 throw new JsonTypeCoersionException("Error calling AddRange on " + targetType.FullName, ex);
1681 }
1682 return collection;
1683 }
1684 else
1685 {
1686 // many ICollection types have an Add method
1687 // which adds items one at a time
1688 method = targetType.GetMethod("Add");
1689 parameters = (method == null) ?
1690 null : method.GetParameters();
1691 paramType = (parameters == null || parameters.Length != 1) ?
1692 null : parameters[0].ParameterType;
1693 if (paramType != null)
1694 {
1695 // loop through adding items to collection
1696 foreach (object item in (IEnumerable)value)
1697 {
1698 try
1699 {
1700 // always try-catch Invoke() to expose real exception
1701 method.Invoke(
1702 collection,
1703 new object[] {
1704 this.CoerceType(paramType, item)
1705 });
1706 }
1707 catch (TargetInvocationException ex)
1708 {
1709 if (ex.InnerException != null)
1710 {
1711 throw new JsonTypeCoersionException(ex.InnerException.Message, ex.InnerException);
1712 }
1713 throw new JsonTypeCoersionException("Error calling Add on " + targetType.FullName, ex);
1714 }
1715 }
1716 return collection;
1717 }
1718 }
1719
1720 try
1721 {
1722 // fall back to basics
1723 return Convert.ChangeType(value, targetType);
1724 }
1725 catch (Exception ex)
1726 {
1727 throw new JsonTypeCoersionException(String.Format("Error converting {0} to {1}", value.GetType().FullName, targetType.FullName), ex);
1728 }
1729 }
1730
1731 private static bool IsNullable(Type type)
1732 {
1733 return type.IsGenericType && (typeof(Nullable<>) == type.GetGenericTypeDefinition());
1734 }
1735
1736 #endregion Type Methods
1737 }
1738
1739 #endregion TypeCoercionUtility
1740 }
1741}
Note: See TracBrowser for help on using the repository browser.