root / trunk / hammock / src / net35 / Hammock / Serialization / JsonParser.cs @ 0eea575a
History | View | Annotate | Download (29.7 kB)
1 |
#region Public Domain License |
---|---|
2 |
/* |
3 |
* JSON Parser |
4 |
* Written by Daniel Crenna |
5 |
* (http://danielcrenna.com) |
6 |
* |
7 |
* This work is public domain. |
8 |
* |
9 |
* "The person who associated a work with this document has |
10 |
* dedicated the work to the Commons by waiving all of his |
11 |
* or her rights to the work worldwide under copyright law |
12 |
* and all related or neighboring legal rights he or she |
13 |
* had in the work, to the extent allowable by law." |
14 |
* |
15 |
* For more information, please visit: |
16 |
* http://creativecommons.org/publicdomain/zero/1.0/ |
17 |
*/ |
18 |
#endregion |
19 |
|
20 |
using System; |
21 |
using System.Collections; |
22 |
using System.Collections.Generic; |
23 |
using System.Globalization; |
24 |
using System.Linq; |
25 |
using System.Reflection; |
26 |
using System.Text; |
27 |
#if NET40 |
28 |
using System.Dynamic; |
29 |
#endif |
30 |
#if NETCF |
31 |
using Hammock.Extensions; |
32 |
#endif |
33 |
|
34 |
|
35 |
namespace Hammock.Serialization |
36 |
{ |
37 |
/// <summary> |
38 |
/// Possible JSON tokens in parsed input. |
39 |
/// </summary> |
40 |
public enum JsonToken |
41 |
{ |
42 |
Unknown, |
43 |
LeftBrace, |
44 |
RightBrace, |
45 |
Colon, |
46 |
Comma, |
47 |
LeftBracket, |
48 |
RightBracket, |
49 |
String, |
50 |
Number, |
51 |
True, |
52 |
False, |
53 |
Null |
54 |
} |
55 |
|
56 |
/// <summary> |
57 |
/// Exception raised when <see cref="JsonParser" /> encounters an invalid token. |
58 |
/// </summary> |
59 |
public class InvalidJsonException : Exception |
60 |
{ |
61 |
public InvalidJsonException(string message) |
62 |
: base(message) |
63 |
{ |
64 |
|
65 |
} |
66 |
} |
67 |
|
68 |
#if NET40 |
69 |
public interface IJson { } |
70 |
|
71 |
public class JsonArray : DynamicObject, IEnumerable, IJson |
72 |
{ |
73 |
private readonly List<IJson> _collection; |
74 |
|
75 |
public JsonArray(ICollection<object> collection) |
76 |
{ |
77 |
_collection = new List<IJson>(collection.Count); |
78 |
foreach (var instance in collection.Cast<IDictionary<string, object>>()) |
79 |
{ |
80 |
_collection.Add(new JsonObject(instance)); |
81 |
} |
82 |
} |
83 |
|
84 |
public IEnumerator GetEnumerator() |
85 |
{ |
86 |
return _collection.GetEnumerator(); |
87 |
} |
88 |
} |
89 |
|
90 |
public class JsonObject : DynamicObject, IJson |
91 |
{ |
92 |
private readonly IDictionary<string, object> _hash = new Dictionary<string, object>(); |
93 |
|
94 |
public JsonObject(IDictionary<string, object> hash) |
95 |
{ |
96 |
_hash = hash; |
97 |
} |
98 |
|
99 |
public override bool TrySetMember(SetMemberBinder binder, object value) |
100 |
{ |
101 |
var name = Underscored(binder.Name); |
102 |
_hash[name] = value; |
103 |
return _hash[name] == value; |
104 |
} |
105 |
|
106 |
public override bool TryGetMember(GetMemberBinder binder, out object result) |
107 |
{ |
108 |
var name = Underscored(binder.Name); |
109 |
return YieldMember(name, out result); |
110 |
} |
111 |
|
112 |
private bool YieldMember(string name, out object result) |
113 |
{ |
114 |
if (_hash.ContainsKey(name)) |
115 |
{ |
116 |
result = _hash[name]; |
117 |
|
118 |
if (result is IDictionary<string, object>) |
119 |
{ |
120 |
result = new JsonObject((IDictionary<string, object>)result); |
121 |
return true; |
122 |
} |
123 |
|
124 |
return _hash[name] == result; |
125 |
} |
126 |
result = null; |
127 |
return false; |
128 |
} |
129 |
|
130 |
private static string Underscored(IEnumerable<char> pascalCase) |
131 |
{ |
132 |
var sb = new StringBuilder(); |
133 |
var i = 0; |
134 |
foreach (var c in pascalCase) |
135 |
{ |
136 |
if (char.IsUpper(c) && i > 0) |
137 |
{ |
138 |
sb.Append("_"); |
139 |
} |
140 |
sb.Append(c); |
141 |
i++; |
142 |
} |
143 |
return sb.ToString().ToLowerInvariant(); |
144 |
} |
145 |
} |
146 |
#endif |
147 |
|
148 |
/// <summary> |
149 |
/// A parser for JSON. |
150 |
/// <seealso cref="http://json.org" /> |
151 |
/// </summary> |
152 |
public class JsonParser |
153 |
{ |
154 |
#if !NETCF |
155 |
private const NumberStyles JsonNumbers = NumberStyles.Float; |
156 |
#endif |
157 |
private static readonly IDictionary<Type, PropertyInfo[]> _cache; |
158 |
|
159 |
private static readonly char[] _base16 = new[] |
160 |
{ |
161 |
'0', '1', '2', '3', |
162 |
'4', '5', '6', '7', |
163 |
'8', '9', 'A', 'B', |
164 |
'C', 'D', 'E', 'F' |
165 |
}; |
166 |
|
167 |
static JsonParser() |
168 |
{ |
169 |
_cache = new Dictionary<Type, PropertyInfo[]>(0); |
170 |
} |
171 |
|
172 |
public static string Serialize<T>(T instance) |
173 |
{ |
174 |
var bag = GetBagForObject(instance); |
175 |
|
176 |
return ToJson(bag); |
177 |
} |
178 |
|
179 |
public static object Deserialize(string json, Type type) |
180 |
{ |
181 |
object instance; |
182 |
var map = PrepareInstance(out instance, type); |
183 |
var bag = FromJson(json); |
184 |
|
185 |
DeserializeImpl(map, bag, instance); |
186 |
return instance; |
187 |
} |
188 |
|
189 |
public static T Deserialize<T>(string json) |
190 |
{ |
191 |
T instance; |
192 |
var map = PrepareInstance(out instance); |
193 |
var bag = FromJson(json); |
194 |
|
195 |
DeserializeImpl(map, bag, instance); |
196 |
return instance; |
197 |
} |
198 |
|
199 |
#if NET40 |
200 |
public static dynamic Deserialize(string json) |
201 |
{ |
202 |
JsonToken type; |
203 |
var inner = FromJson(json, out type); |
204 |
dynamic instance = null; |
205 |
|
206 |
switch (type) |
207 |
{ |
208 |
case JsonToken.LeftBrace: |
209 |
var @object = (IDictionary<string, object>)inner.Single().Value; |
210 |
instance = new JsonObject(@object); |
211 |
break; |
212 |
case JsonToken.LeftBracket: |
213 |
var @array = (IList<object>)inner.Single().Value; |
214 |
instance = new JsonArray(@array); |
215 |
break; |
216 |
} |
217 |
|
218 |
return instance; |
219 |
} |
220 |
#endif |
221 |
|
222 |
private static void DeserializeImpl(IEnumerable<PropertyInfo> map, |
223 |
IDictionary<string, object> bag, |
224 |
object instance) |
225 |
{ |
226 |
DeserializeType(map, bag, instance); |
227 |
} |
228 |
|
229 |
private static void DeserializeImpl<T>(IEnumerable<PropertyInfo> map, |
230 |
IDictionary<string, object> bag, |
231 |
T instance) |
232 |
{ |
233 |
DeserializeType(map, bag, instance); |
234 |
} |
235 |
|
236 |
private static void DeserializeType(IEnumerable<PropertyInfo> map, IDictionary<string, object> bag, object instance) |
237 |
{ |
238 |
foreach (var info in map) |
239 |
{ |
240 |
var key = info.Name; |
241 |
if (!bag.ContainsKey(key)) |
242 |
{ |
243 |
key = info.Name.Replace("_", ""); |
244 |
if (!bag.ContainsKey(key)) |
245 |
{ |
246 |
key = info.Name.Replace("-", ""); |
247 |
if (!bag.ContainsKey(key)) |
248 |
{ |
249 |
continue; |
250 |
} |
251 |
} |
252 |
} |
253 |
|
254 |
var value = bag[key]; |
255 |
if (info.PropertyType == typeof(DateTime)) |
256 |
{ |
257 |
// Dates (Not part of spec, using lossy epoch convention) |
258 |
var seconds = Int32.Parse( |
259 |
value.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture |
260 |
); |
261 |
var time = new DateTime(1970, 1, 1).ToUniversalTime(); |
262 |
value = time.AddSeconds(seconds); |
263 |
} |
264 |
|
265 |
if (info.PropertyType == typeof(byte[])) |
266 |
{ |
267 |
var bytes = (List<object>)value; |
268 |
#if NETCF |
269 |
value = bytes.Select(o => Convert.ToByte(o)).ToArray(); |
270 |
#else |
271 |
value = bytes.Select(Convert.ToByte).ToArray(); |
272 |
#endif |
273 |
} |
274 |
|
275 |
if (info.PropertyType == typeof(double)) |
276 |
{ |
277 |
value = Convert.ToDouble(value); |
278 |
} |
279 |
|
280 |
if (info.PropertyType == typeof(int)) |
281 |
{ |
282 |
value = Convert.ToInt32(value); |
283 |
} |
284 |
|
285 |
if (info.PropertyType == typeof(long)) |
286 |
{ |
287 |
value = Convert.ToInt64(value); |
288 |
} |
289 |
|
290 |
info.SetValue(instance, value, null); |
291 |
} |
292 |
} |
293 |
|
294 |
public static IDictionary<string, object> FromJson(string json) |
295 |
{ |
296 |
JsonToken type; |
297 |
|
298 |
var result = FromJson(json, out type); |
299 |
|
300 |
switch (type) |
301 |
{ |
302 |
case JsonToken.LeftBrace: |
303 |
var @object = (IDictionary<string, object>)result.Single().Value; |
304 |
return @object; |
305 |
} |
306 |
|
307 |
return result; |
308 |
} |
309 |
|
310 |
public static IDictionary<string, object> FromJson(string json, out JsonToken type) |
311 |
{ |
312 |
var data = json.ToCharArray(); |
313 |
var index = 0; |
314 |
|
315 |
// Rewind index for first token |
316 |
var token = NextToken(data, ref index); |
317 |
switch (token) |
318 |
{ |
319 |
case JsonToken.LeftBrace: // Start Object |
320 |
case JsonToken.LeftBracket: // Start Array |
321 |
index--; |
322 |
type = token; |
323 |
break; |
324 |
default: |
325 |
throw new InvalidJsonException("JSON must begin with an object or array"); |
326 |
} |
327 |
|
328 |
return ParseObject(data, ref index); |
329 |
} |
330 |
|
331 |
public static string ToJson(IDictionary<string, object> bag) |
332 |
{ |
333 |
var sb = new StringBuilder(0); |
334 |
|
335 |
SerializeItem(sb, bag); |
336 |
|
337 |
return sb.ToString(); |
338 |
} |
339 |
|
340 |
internal static IDictionary<string, object> GetBagForObject(Type type, object instance) |
341 |
{ |
342 |
CacheReflection(type); |
343 |
|
344 |
if (type.FullName == null) |
345 |
{ |
346 |
return null; |
347 |
} |
348 |
|
349 |
var anonymous = type.FullName.Contains("__AnonymousType"); |
350 |
var map = _cache[type]; |
351 |
|
352 |
IDictionary<string, object> bag = InitializeBag(); |
353 |
foreach (var info in map) |
354 |
{ |
355 |
var readWrite = (info.CanWrite && info.CanRead); |
356 |
if (!readWrite && !anonymous) |
357 |
{ |
358 |
continue; |
359 |
} |
360 |
var value = info.GetValue(instance, null); |
361 |
bag.Add(info.Name, value); |
362 |
} |
363 |
|
364 |
return bag; |
365 |
} |
366 |
|
367 |
internal static IDictionary<string, object> GetBagForObject<T>(T instance) |
368 |
{ |
369 |
return GetBagForObject(typeof(T), instance); |
370 |
} |
371 |
|
372 |
internal static Dictionary<string, object> InitializeBag() |
373 |
{ |
374 |
return new Dictionary<string, object>( |
375 |
0, StringComparer.OrdinalIgnoreCase |
376 |
); |
377 |
} |
378 |
|
379 |
internal static IEnumerable<PropertyInfo> PrepareInstance(out object instance, Type type) |
380 |
{ |
381 |
instance = Activator.CreateInstance(type); |
382 |
|
383 |
CacheReflection(type); |
384 |
|
385 |
return _cache[type]; |
386 |
} |
387 |
|
388 |
internal static IEnumerable<PropertyInfo> PrepareInstance<T>(out T instance) |
389 |
{ |
390 |
instance = Activator.CreateInstance<T>(); |
391 |
var item = typeof(T); |
392 |
|
393 |
CacheReflection(item); |
394 |
|
395 |
return _cache[item]; |
396 |
} |
397 |
|
398 |
internal static void CacheReflection(Type item) |
399 |
{ |
400 |
if (_cache.ContainsKey(item)) |
401 |
{ |
402 |
return; |
403 |
} |
404 |
|
405 |
var properties = item.GetProperties( |
406 |
BindingFlags.Public | BindingFlags.Instance |
407 |
); |
408 |
|
409 |
_cache.Add(item, properties); |
410 |
} |
411 |
|
412 |
internal static void SerializeItem(StringBuilder sb, object item) |
413 |
{ |
414 |
if (item is IDictionary<string, object>) |
415 |
{ |
416 |
SerializeObject(item, sb); |
417 |
return; |
418 |
} |
419 |
|
420 |
if (item is IEnumerable) |
421 |
{ |
422 |
SerializeArray(item, sb); |
423 |
return; |
424 |
} |
425 |
|
426 |
if (item is DateTime) |
427 |
{ |
428 |
SerializeDateTime(sb); |
429 |
return; |
430 |
} |
431 |
|
432 |
if (item is bool) |
433 |
{ |
434 |
sb.Append(((bool)item).ToString().ToLower()); |
435 |
return; |
436 |
} |
437 |
|
438 |
double number; |
439 |
var input = item != null ? item.ToString() : ""; |
440 |
#if NETCF |
441 |
if (input.TryParse(out number)) |
442 |
{ |
443 |
sb.Append(number); |
444 |
} |
445 |
#else |
446 |
if (double.TryParse(input, JsonNumbers, CultureInfo.InvariantCulture, out number)) |
447 |
{ |
448 |
sb.Append(number); |
449 |
return; |
450 |
} |
451 |
#endif |
452 |
if (item == null) |
453 |
{ |
454 |
sb.Append("null"); |
455 |
return; |
456 |
} |
457 |
|
458 |
var bag = GetBagForObject(item.GetType(), item); |
459 |
SerializeItem(sb, bag); |
460 |
} |
461 |
|
462 |
internal static void SerializeDateTime(StringBuilder sb) |
463 |
{ |
464 |
var elapsed = DateTime.UtcNow - new DateTime(1970, 1, 1).ToUniversalTime(); |
465 |
var epoch = (long)elapsed.TotalSeconds; |
466 |
SerializeString(sb, epoch); |
467 |
} |
468 |
|
469 |
internal static void SerializeArray(object item, StringBuilder sb) |
470 |
{ |
471 |
var array = (IEnumerable)item; |
472 |
sb.Append("["); |
473 |
var count = 0; |
474 |
|
475 |
var total = array.Cast<object>().Count(); |
476 |
foreach (var element in array) |
477 |
{ |
478 |
SerializeItem(sb, element); |
479 |
count++; |
480 |
if (count < total) |
481 |
{ |
482 |
sb.Append(","); |
483 |
} |
484 |
} |
485 |
sb.Append("]"); |
486 |
} |
487 |
|
488 |
internal static void SerializeObject(object item, StringBuilder sb) |
489 |
{ |
490 |
var nested = (IDictionary<string, object>)item; |
491 |
sb.Append("{"); |
492 |
|
493 |
var count = 0; |
494 |
foreach (var key in nested.Keys) |
495 |
{ |
496 |
SerializeString(sb, key.ToLower()); |
497 |
sb.Append(":"); |
498 |
|
499 |
var value = nested[key]; |
500 |
if (value is string) |
501 |
{ |
502 |
SerializeString(sb, value); |
503 |
} |
504 |
else |
505 |
{ |
506 |
SerializeItem(sb, nested[key]); |
507 |
} |
508 |
|
509 |
if (count < nested.Keys.Count - 1) |
510 |
{ |
511 |
sb.Append(","); |
512 |
} |
513 |
count++; |
514 |
} |
515 |
sb.Append("}"); |
516 |
} |
517 |
|
518 |
|
519 |
internal static void SerializeString(StringBuilder sb, object item) |
520 |
{ |
521 |
sb.Append("\""); |
522 |
var symbols = item.ToString().ToCharArray(); |
523 |
#if NETCF |
524 |
foreach (var unicode in symbols.Select(symbol => (int)symbol).Select(code => GetUnicode(code))) |
525 |
#else |
526 |
foreach (var unicode in symbols.Select(symbol => (int)symbol).Select(GetUnicode)) |
527 |
#endif |
528 |
{ |
529 |
sb.Append(unicode); |
530 |
} |
531 |
sb.Append("\""); |
532 |
} |
533 |
|
534 |
internal static string GetUnicode(int code) |
535 |
{ |
536 |
// http://unicode.org/roadmaps/bmp/ |
537 |
var basicLatin = code >= 32 && code <= 126; |
538 |
if (basicLatin) |
539 |
{ |
540 |
return new string((char)code, 1); |
541 |
} |
542 |
|
543 |
var unicode = BaseConvert(code, _base16, 4); |
544 |
return string.Concat("\\u", unicode); |
545 |
} |
546 |
|
547 |
internal static KeyValuePair<string, object> ParsePair(IList<char> data, ref int index) |
548 |
{ |
549 |
var valid = true; |
550 |
|
551 |
var name = ParseString(data, ref index); |
552 |
if (name == null) |
553 |
{ |
554 |
valid = false; |
555 |
} |
556 |
|
557 |
if (!ParseToken(JsonToken.Colon, data, ref index)) |
558 |
{ |
559 |
valid = false; |
560 |
} |
561 |
|
562 |
if (!valid) |
563 |
{ |
564 |
throw new InvalidJsonException(string.Format( |
565 |
"Invalid JSON found while parsing a value pair at index {0}.", index |
566 |
)); |
567 |
} |
568 |
|
569 |
index++; |
570 |
var value = ParseValue(data, ref index); |
571 |
return new KeyValuePair<string, object>(name, value); |
572 |
} |
573 |
|
574 |
internal static bool ParseToken(JsonToken token, IList<char> data, ref int index) |
575 |
{ |
576 |
var nextToken = NextToken(data, ref index); |
577 |
return token == nextToken; |
578 |
} |
579 |
|
580 |
internal static string ParseString(IList<char> data, ref int index) |
581 |
{ |
582 |
var symbol = data[index]; |
583 |
IgnoreWhitespace(data, ref index, symbol); |
584 |
symbol = data[++index]; // Skip first quotation |
585 |
|
586 |
var sb = new StringBuilder(); |
587 |
while (true) |
588 |
{ |
589 |
if (index >= data.Count - 1) |
590 |
{ |
591 |
return null; |
592 |
} |
593 |
switch (symbol) |
594 |
{ |
595 |
case '"': // End String |
596 |
index++; |
597 |
return sb.ToString(); |
598 |
case '\\': // Control Character |
599 |
symbol = data[++index]; |
600 |
switch (symbol) |
601 |
{ |
602 |
case '/': |
603 |
sb.Append(symbol); |
604 |
break; |
605 |
case '\\': |
606 |
case 'b': |
607 |
case 'f': |
608 |
case 'n': |
609 |
case 'r': |
610 |
case 't': |
611 |
break; |
612 |
case 'u': // Unicode literals |
613 |
if (index < data.Count - 5) |
614 |
{ |
615 |
var array = data.ToArray(); |
616 |
var buffer = new char[4]; |
617 |
Array.Copy(array, index + 1, buffer, 0, 4); |
618 |
|
619 |
// http://msdn.microsoft.com/en-us/library/aa664669%28VS.71%29.aspx |
620 |
// http://www.yoda.arachsys.com/csharp/unicode.html |
621 |
// http://en.wikipedia.org/wiki/UTF-32/UCS-4 |
622 |
var hex = new string(buffer); |
623 |
var unicode = (char)Convert.ToInt32(hex, 16); |
624 |
sb.Append(unicode); |
625 |
index += 4; |
626 |
} |
627 |
else |
628 |
{ |
629 |
break; |
630 |
} |
631 |
break; |
632 |
} |
633 |
break; |
634 |
default: |
635 |
sb.Append(symbol); |
636 |
break; |
637 |
} |
638 |
symbol = data[++index]; |
639 |
} |
640 |
} |
641 |
|
642 |
internal static object ParseValue(IList<char> data, ref int index) |
643 |
{ |
644 |
var token = NextToken(data, ref index); |
645 |
|
646 |
switch (token) |
647 |
{ |
648 |
// End Tokens |
649 |
case JsonToken.RightBracket: // Bad Data |
650 |
case JsonToken.RightBrace: |
651 |
case JsonToken.Unknown: |
652 |
case JsonToken.Colon: |
653 |
case JsonToken.Comma: |
654 |
throw new InvalidJsonException(string.Format( |
655 |
"Invalid JSON found while parsing a value at index {0}.", index |
656 |
)); |
657 |
// Value Tokens |
658 |
case JsonToken.LeftBrace: |
659 |
return ParseObject(data, ref index); |
660 |
case JsonToken.LeftBracket: |
661 |
return ParseArray(data, ref index); |
662 |
case JsonToken.String: |
663 |
return ParseString(data, ref index); |
664 |
case JsonToken.Number: |
665 |
return ParseNumber(data, ref index); |
666 |
case JsonToken.True: |
667 |
return true; |
668 |
case JsonToken.False: |
669 |
return false; |
670 |
case JsonToken.Null: |
671 |
return null; |
672 |
default: |
673 |
throw new ArgumentOutOfRangeException(); |
674 |
} |
675 |
} |
676 |
|
677 |
internal static IDictionary<string, object> ParseObject(IList<char> data, ref int index) |
678 |
{ |
679 |
var result = InitializeBag(); |
680 |
|
681 |
index++; // Skip first token |
682 |
|
683 |
while (index < data.Count - 1) |
684 |
{ |
685 |
var token = NextToken(data, ref index); |
686 |
switch (token) |
687 |
{ |
688 |
// End Tokens |
689 |
case JsonToken.Unknown: // Bad Data |
690 |
case JsonToken.True: |
691 |
case JsonToken.False: |
692 |
case JsonToken.Null: |
693 |
case JsonToken.Colon: |
694 |
case JsonToken.RightBracket: |
695 |
case JsonToken.Number: |
696 |
throw new InvalidJsonException(string.Format( |
697 |
"Invalid JSON found while parsing an object at index {0}.", index |
698 |
)); |
699 |
case JsonToken.RightBrace: // End Object |
700 |
index++; |
701 |
return result; |
702 |
// Skip Tokens |
703 |
case JsonToken.Comma: |
704 |
index++; |
705 |
break; |
706 |
// Start Tokens |
707 |
case JsonToken.LeftBrace: // Start Object |
708 |
var @object = ParseObject(data, ref index); |
709 |
if (@object != null) |
710 |
{ |
711 |
result.Add(string.Concat("object", result.Count), @object); |
712 |
} |
713 |
index++; |
714 |
break; |
715 |
case JsonToken.LeftBracket: // Start Array |
716 |
var @array = ParseArray(data, ref index); |
717 |
if (@array != null) |
718 |
{ |
719 |
result.Add(string.Concat("array", result.Count), @array); |
720 |
} |
721 |
index++; |
722 |
break; |
723 |
case JsonToken.String: |
724 |
var pair = ParsePair(data, ref index); |
725 |
result.Add(pair.Key, pair.Value); |
726 |
break; |
727 |
default: |
728 |
throw new NotSupportedException("Invalid token expected."); |
729 |
} |
730 |
} |
731 |
|
732 |
return result; |
733 |
} |
734 |
|
735 |
internal static IEnumerable<object> ParseArray(IList<char> data, ref int index) |
736 |
{ |
737 |
var result = new List<object>(); |
738 |
|
739 |
index++; // Skip first bracket |
740 |
while (index < data.Count - 1) |
741 |
{ |
742 |
var token = NextToken(data, ref index); |
743 |
switch (token) |
744 |
{ |
745 |
// End Tokens |
746 |
case JsonToken.Unknown: // Bad Data |
747 |
throw new InvalidJsonException(string.Format( |
748 |
"Invalid JSON found while parsing an array at index {0}.", index |
749 |
)); |
750 |
case JsonToken.RightBracket: // End Array |
751 |
index++; |
752 |
return result; |
753 |
// Skip Tokens |
754 |
case JsonToken.Comma: // Separator |
755 |
case JsonToken.RightBrace: // End Object |
756 |
case JsonToken.Colon: // Separator |
757 |
index++; |
758 |
break; |
759 |
// Value Tokens |
760 |
case JsonToken.LeftBrace: // Start Object |
761 |
var nested = ParseObject(data, ref index); |
762 |
result.Add(nested); |
763 |
break; |
764 |
case JsonToken.LeftBracket: // Start Array |
765 |
case JsonToken.String: |
766 |
case JsonToken.Number: |
767 |
case JsonToken.True: |
768 |
case JsonToken.False: |
769 |
case JsonToken.Null: |
770 |
var value = ParseValue(data, ref index); |
771 |
result.Add(value); |
772 |
break; |
773 |
default: |
774 |
throw new ArgumentOutOfRangeException(); |
775 |
} |
776 |
|
777 |
//index++; |
778 |
} |
779 |
|
780 |
return result; |
781 |
} |
782 |
|
783 |
internal static object ParseNumber(IList<char> data, ref int index) |
784 |
{ |
785 |
var symbol = data[index]; |
786 |
IgnoreWhitespace(data, ref index, symbol); |
787 |
|
788 |
var start = index; |
789 |
var length = 0; |
790 |
while (ParseToken(JsonToken.Number, data, ref index)) |
791 |
{ |
792 |
length++; |
793 |
index++; |
794 |
} |
795 |
|
796 |
var number = new char[length]; |
797 |
Array.Copy(data.ToArray(), start, number, 0, length); |
798 |
|
799 |
double result; |
800 |
var buffer = new string(number); |
801 |
#if NETCF |
802 |
if (!buffer.TryParse(out result)) |
803 |
{ |
804 |
throw new InvalidJsonException( |
805 |
string.Format("Value '{0}' was not a valid JSON number", buffer) |
806 |
); |
807 |
} |
808 |
#else |
809 |
if (!double.TryParse(buffer, JsonNumbers, CultureInfo.InvariantCulture, out result)) |
810 |
{ |
811 |
throw new InvalidJsonException( |
812 |
string.Format("Value '{0}' was not a valid JSON number", buffer) |
813 |
); |
814 |
} |
815 |
#endif |
816 |
|
817 |
return result; |
818 |
} |
819 |
|
820 |
internal static JsonToken NextToken(IList<char> data, ref int index) |
821 |
{ |
822 |
var symbol = data[index]; |
823 |
var token = GetTokenFromSymbol(symbol); |
824 |
token = IgnoreWhitespace(data, ref index, ref token, symbol); |
825 |
|
826 |
GetKeyword("true", JsonToken.True, data, ref index, ref token); |
827 |
GetKeyword("false", JsonToken.False, data, ref index, ref token); |
828 |
GetKeyword("null", JsonToken.Null, data, ref index, ref token); |
829 |
|
830 |
return token; |
831 |
} |
832 |
|
833 |
internal static JsonToken GetTokenFromSymbol(char symbol) |
834 |
{ |
835 |
return GetTokenFromSymbol(symbol, JsonToken.Unknown); |
836 |
} |
837 |
|
838 |
internal static JsonToken GetTokenFromSymbol(char symbol, JsonToken token) |
839 |
{ |
840 |
switch (symbol) |
841 |
{ |
842 |
case '{': |
843 |
token = JsonToken.LeftBrace; |
844 |
break; |
845 |
case '}': |
846 |
token = JsonToken.RightBrace; |
847 |
break; |
848 |
case ':': |
849 |
token = JsonToken.Colon; |
850 |
break; |
851 |
case ',': |
852 |
token = JsonToken.Comma; |
853 |
break; |
854 |
case '[': |
855 |
token = JsonToken.LeftBracket; |
856 |
break; |
857 |
case ']': |
858 |
token = JsonToken.RightBracket; |
859 |
break; |
860 |
case '"': |
861 |
token = JsonToken.String; |
862 |
break; |
863 |
case '0': |
864 |
case '1': |
865 |
case '2': |
866 |
case '3': |
867 |
case '4': |
868 |
case '5': |
869 |
case '6': |
870 |
case '7': |
871 |
case '8': |
872 |
case '9': |
873 |
case '.': |
874 |
case 'e': |
875 |
case 'E': |
876 |
case '+': |
877 |
case '-': |
878 |
token = JsonToken.Number; |
879 |
break; |
880 |
} |
881 |
return token; |
882 |
} |
883 |
|
884 |
internal static void IgnoreWhitespace(IList<char> data, ref int index, char symbol) |
885 |
{ |
886 |
var token = JsonToken.Unknown; |
887 |
IgnoreWhitespace(data, ref index, ref token, symbol); |
888 |
return; |
889 |
} |
890 |
|
891 |
internal static JsonToken IgnoreWhitespace(IList<char> data, ref int index, ref JsonToken token, char symbol) |
892 |
{ |
893 |
switch (symbol) |
894 |
{ |
895 |
case ' ': |
896 |
case '\\': |
897 |
case '/': |
898 |
case '\b': |
899 |
case '\f': |
900 |
case '\n': |
901 |
case '\r': |
902 |
case '\t': |
903 |
index++; |
904 |
token = NextToken(data, ref index); |
905 |
break; |
906 |
} |
907 |
return token; |
908 |
} |
909 |
|
910 |
internal static void GetKeyword(string word, |
911 |
JsonToken target, |
912 |
IList<char> data, |
913 |
ref int index, |
914 |
ref JsonToken result) |
915 |
{ |
916 |
var buffer = data.Count - index; |
917 |
if (buffer < word.Length) |
918 |
{ |
919 |
return; |
920 |
} |
921 |
|
922 |
for (var i = 0; i < word.Length; i++) |
923 |
{ |
924 |
if (data[index + i] != word[i]) |
925 |
{ |
926 |
return; |
927 |
} |
928 |
} |
929 |
|
930 |
result = target; |
931 |
index += word.Length; |
932 |
} |
933 |
|
934 |
internal static string BaseConvert(int input, char[] charSet, int minLength) |
935 |
{ |
936 |
var sb = new StringBuilder(); |
937 |
var @base = charSet.Length; |
938 |
|
939 |
while (input > 0) |
940 |
{ |
941 |
var index = input % @base; |
942 |
sb.Insert(0, new[] { charSet[index] }); |
943 |
input = input / @base; |
944 |
} |
945 |
|
946 |
while (sb.Length < minLength) |
947 |
{ |
948 |
sb.Insert(0, "0"); |
949 |
} |
950 |
|
951 |
return sb.ToString(); |
952 |
} |
953 |
} |
954 |
|
955 |
#if NETCF |
956 |
public static class CompactExtensions |
957 |
{ |
958 |
private const NumberStyles JsonNumbers = NumberStyles.Float; |
959 |
|
960 |
public static bool TryParse(this string input, out double result) |
961 |
{ |
962 |
try |
963 |
{ |
964 |
result = double.Parse(input, JsonNumbers, CultureInfo.InvariantCulture); |
965 |
return true; |
966 |
} |
967 |
catch (Exception) |
968 |
{ |
969 |
result = 0; |
970 |
return false; |
971 |
} |
972 |
} |
973 |
} |
974 |
#endif |
975 |
} |
976 |
|