// // JsonDeserializer.cs // // Author: // Marek Habersack // // (C) 2008 Novell, Inc. http://novell.com/ // Copyright 2011, Xamarin, Inc (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Code is based on JSON_checker (http://www.json.org/JSON_checker/) and JSON_parser // (http://fara.cs.uni-potsdam.de/~jsg/json_parser/) C sources. using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.Reflection; using System.Text; namespace System.Web.Script.Serialization { internal sealed class JsonDeserializer { /* Universal error constant */ const int __ = -1; const int UNIVERSAL_ERROR = __; /* Characters are mapped into these 31 character classes. This allows for a significant reduction in the size of the state transition table. */ const int C_SPACE = 0x00; /* space */ const int C_WHITE = 0x01; /* other whitespace */ const int C_LCURB = 0x02; /* { */ const int C_RCURB = 0x03; /* } */ const int C_LSQRB = 0x04; /* [ */ const int C_RSQRB = 0x05; /* ] */ const int C_COLON = 0x06; /* : */ const int C_COMMA = 0x07; /* , */ const int C_QUOTE = 0x08; /* " */ const int C_BACKS = 0x09; /* \ */ const int C_SLASH = 0x0A; /* / */ const int C_PLUS = 0x0B; /* + */ const int C_MINUS = 0x0C; /* - */ const int C_POINT = 0x0D; /* . */ const int C_ZERO = 0x0E; /* 0 */ const int C_DIGIT = 0x0F; /* 123456789 */ const int C_LOW_A = 0x10; /* a */ const int C_LOW_B = 0x11; /* b */ const int C_LOW_C = 0x12; /* c */ const int C_LOW_D = 0x13; /* d */ const int C_LOW_E = 0x14; /* e */ const int C_LOW_F = 0x15; /* f */ const int C_LOW_L = 0x16; /* l */ const int C_LOW_N = 0x17; /* n */ const int C_LOW_R = 0x18; /* r */ const int C_LOW_S = 0x19; /* s */ const int C_LOW_T = 0x1A; /* t */ const int C_LOW_U = 0x1B; /* u */ const int C_ABCDF = 0x1C; /* ABCDF */ const int C_E = 0x1D; /* E */ const int C_ETC = 0x1E; /* everything else */ const int C_STAR = 0x1F; /* * */ const int C_I = 0x20; /* I */ const int C_LOW_I = 0x21; /* i */ const int C_LOW_Y = 0x22; /* y */ const int C_N = 0x23; /* N */ /* The state codes. */ const int GO = 0x00; /* start */ const int OK = 0x01; /* ok */ const int OB = 0x02; /* object */ const int KE = 0x03; /* key */ const int CO = 0x04; /* colon */ const int VA = 0x05; /* value */ const int AR = 0x06; /* array */ const int ST = 0x07; /* string */ const int ES = 0x08; /* escape */ const int U1 = 0x09; /* u1 */ const int U2 = 0x0A; /* u2 */ const int U3 = 0x0B; /* u3 */ const int U4 = 0x0C; /* u4 */ const int MI = 0x0D; /* minus */ const int ZE = 0x0E; /* zero */ const int IN = 0x0F; /* integer */ const int FR = 0x10; /* fraction */ const int E1 = 0x11; /* e */ const int E2 = 0x12; /* ex */ const int E3 = 0x13; /* exp */ const int T1 = 0x14; /* tr */ const int T2 = 0x15; /* tru */ const int T3 = 0x16; /* true */ const int F1 = 0x17; /* fa */ const int F2 = 0x18; /* fal */ const int F3 = 0x19; /* fals */ const int F4 = 0x1A; /* false */ const int N1 = 0x1B; /* nu */ const int N2 = 0x1C; /* nul */ const int N3 = 0x1D; /* null */ const int FX = 0x1E; /* *.* *eE* */ const int IV = 0x1F; /* invalid input */ const int UK = 0x20; /* unquoted key name */ const int UI = 0x21; /* ignore during unquoted key name construction */ const int I1 = 0x22; /* In */ const int I2 = 0x23; /* Inf */ const int I3 = 0x24; /* Infi */ const int I4 = 0x25; /* Infin */ const int I5 = 0x26; /* Infini */ const int I6 = 0x27; /* Infinit */ const int I7 = 0x28; /* Infinity */ const int V1 = 0x29; /* Na */ const int V2 = 0x2A; /* NaN */ /* Actions */ const int FA = -10; /* false */ const int TR = -11; /* false */ const int NU = -12; /* null */ const int DE = -13; /* double detected by exponent e E */ const int DF = -14; /* double detected by fraction . */ const int SB = -15; /* string begin */ const int MX = -16; /* integer detected by minus */ const int ZX = -17; /* integer detected by zero */ const int IX = -18; /* integer detected by 1-9 */ const int EX = -19; /* next char is escaped */ const int UC = -20; /* Unicode character read */ const int SE = -4; /* string end */ const int AB = -5; /* array begin */ const int AE = -7; /* array end */ const int OS = -6; /* object start */ const int OE = -8; /* object end */ const int EO = -9; /* empty object */ const int CM = -3; /* comma */ const int CA = -2; /* colon action */ const int PX = -21; /* integer detected by plus */ const int KB = -22; /* unquoted key name begin */ const int UE = -23; /* unquoted key name end */ const int IF = -25; /* Infinity */ const int NN = -26; /* NaN */ enum JsonMode { NONE, ARRAY, DONE, KEY, OBJECT }; enum JsonType { NONE = 0, ARRAY_BEGIN, ARRAY_END, OBJECT_BEGIN, OBJECT_END, INTEGER, FLOAT, NULL, TRUE, FALSE, STRING, KEY, MAX }; /* This array maps the 128 ASCII characters into character classes. The remaining Unicode characters should be mapped to C_ETC. Non-whitespace control characters are errors. */ static readonly int[] ascii_class = { __, __, __, __, __, __, __, __, __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE, C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH, C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC, C_ETC, C_I, C_ETC, C_ETC, C_ETC, C_ETC, C_N, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC, C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC, C_ETC, C_LOW_I, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC, C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC, C_ETC, C_LOW_Y, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC }; static readonly int[,] state_transition_table = { /* The state transition table takes the current state and the current symbol, and returns either a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the text the state is OK and if the mode is MODE_DONE. white ' 1-9 ABCDF etc space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * I i y N */ /*start GO*/ {GO,GO,OS,__,AB,__,__,__,SB,__,__,PX,MX,__,ZX,IX,__,__,__,__,__,FA,__,__,__,__,TR,__,__,__,__,__,I1,__,__,V1}, /*ok OK*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*object OB*/ {OB,OB,__,EO,__,__,__,__,SB,__,__,__,KB,__,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,__,KB,KB,KB,KB}, /*key KE*/ {KE,KE,__,__,__,__,__,__,SB,__,__,__,KB,__,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,KB,__,KB,KB,KB,KB}, /*colon CO*/ {CO,CO,__,__,__,__,CA,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*value VA*/ {VA,VA,OS,__,AB,__,__,__,SB,__,__,PX,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__,I1,__,__,V1}, /*array AR*/ {AR,AR,OS,__,AB,AE,__,__,SB,__,__,PX,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__,I1,__,__,V1}, /*string ST*/ {ST,ST,ST,ST,ST,ST,ST,ST,SE,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST}, /*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__,__,__,__,__}, /*u1 U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__,__,__,__,__}, /*u2 U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__,__,__,__,__}, /*u3 U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__,__,__,__,__}, /*u4 U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__,__,__,__,__}, /*minus MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IN,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I1,__,__,__}, /*zero ZE*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*int IN*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,DF,IN,IN,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__,__,__,__,__}, /*frac FR*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__,__,__,__,__}, /*e E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*ex E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*exp E3*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*tr T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__,__,__,__,__}, /*tru T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__,__,__,__,__}, /*true T3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*fa F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*fal F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*fals F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__,__,__,__,__}, /*false F4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*nu N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__,__,__,__,__}, /*nul N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*null N3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*_. FX*/ {OK,OK,__,OE,__,AE,__,CM,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__,__,__,__,__}, /*inval. IV*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*unq.key UK*/ {UI,UI,__,__,__,__,UE,__,__,__,__,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,UK,__,UK,UK,UK,UK}, /*unq.ign. UI*/ {UI,UI,__,__,__,__,UE,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*i1 I1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I2,__,__,__,__,__,__,__,__,__,__,__,__}, /*i2 I2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I3,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*i3 I3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I4,__,__}, /*i4 I4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I5,__,__,__,__,__,__,__,__,__,__,__,__}, /*i5 I5*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I6,__,__}, /*i6 I6*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,I7,__,__,__,__,__,__,__,__,__}, /*i7 I7*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,IF,__}, /*v1 V1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,V2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*v2 V2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,NN}, }; JavaScriptSerializer serializer; JavaScriptTypeResolver typeResolver; int maxJsonLength; int currentPosition; int recursionLimit; int recursionDepth; Stack modes; Stack returnValue; JsonType jsonType; bool escaped; int state; Stack currentKey; StringBuilder buffer; char quoteChar; public JsonDeserializer (JavaScriptSerializer serializer) { this.serializer = serializer; this.maxJsonLength = serializer.MaxJsonLength; this.recursionLimit = serializer.RecursionLimit; this.typeResolver = serializer.TypeResolver; this.modes = new Stack (); this.currentKey = new Stack (); this.returnValue = new Stack (); this.state = GO; this.currentPosition = 0; this.recursionDepth = 0; } public object Deserialize (string input) { if (input == null) throw new ArgumentNullException ("input"); return Deserialize (new StringReader (input)); } public object Deserialize (TextReader input) { if (input == null) throw new ArgumentNullException ("input"); int value; buffer = new StringBuilder (); while (true) { value = input.Read (); if (value < 0) break; currentPosition++; if (currentPosition > maxJsonLength) throw new ArgumentException ("Maximum JSON input length has been exceeded."); if (!ProcessCharacter ((char) value)) throw new InvalidOperationException ("JSON syntax error."); } object topObject = PeekObject (); if (buffer.Length > 0) { object result; if (ParseBuffer (out result)) { if (topObject != null) StoreValue (result); else PushObject (result); } } if (returnValue.Count > 1) throw new InvalidOperationException ("JSON syntax error."); object ret = PopObject (); return ret; } #if DEBUG void DumpObject (string indent, object obj) { if (obj is Dictionary ) { Console.WriteLine (indent + "{"); foreach (KeyValuePair kvp in (Dictionary )obj) { Console.WriteLine (indent + "\t\"{0}\": ", kvp.Key); DumpObject (indent + "\t\t", kvp.Value); } Console.WriteLine (indent + "}"); } else if (obj is object[]) { Console.WriteLine (indent + "["); foreach (object o in (object[])obj) DumpObject (indent + "\t", o); Console.WriteLine (indent + "]"); } else if (obj != null) Console.WriteLine (indent + obj.ToString ()); else Console.WriteLine ("null"); } #endif void DecodeUnicodeChar () { int len = buffer.Length; if (len < 6) throw new ArgumentException ("Invalid escaped unicode character specification (" + currentPosition + ")"); int code = Int32.Parse (buffer.ToString ().Substring (len - 4), NumberStyles.HexNumber); buffer.Length = len - 6; buffer.Append ((char)code); } string GetModeMessage (JsonMode expectedMode) { switch (expectedMode) { case JsonMode.ARRAY: return "Invalid array passed in, ',' or ']' expected (" + currentPosition + ")"; case JsonMode.KEY: return "Invalid object passed in, key name or ':' expected (" + currentPosition + ")"; case JsonMode.OBJECT: return "Invalid object passed in, key value expected (" + currentPosition + ")"; default: return "Invalid JSON string"; } } void PopMode (JsonMode expectedMode) { JsonMode mode = PeekMode (); if (mode != expectedMode) throw new ArgumentException (GetModeMessage (mode)); modes.Pop (); } void PushMode (JsonMode newMode) { modes.Push (newMode); } JsonMode PeekMode () { if (modes.Count == 0) return JsonMode.NONE; return modes.Peek (); } void PushObject (object o) { returnValue.Push (o); } object PopObject (bool notIfLast) { int count = returnValue.Count; if (count == 0) return null; if (notIfLast && count == 1) return null; return returnValue.Pop (); } object PopObject () { return PopObject (false); } object PeekObject () { if (returnValue.Count == 0) return null; return returnValue.Peek (); } void RemoveLastCharFromBuffer () { int len = buffer.Length; if (len == 0) return; buffer.Length = len - 1; } bool ParseBuffer (out object result) { result = null; if (jsonType == JsonType.NONE) { buffer.Length = 0; return false; } string s = buffer.ToString (); bool converted = true; int intValue; long longValue; decimal decimalValue; double doubleValue; switch (jsonType) { case JsonType.INTEGER: /* MS AJAX.NET JSON parser promotes big integers to double */ if (Int32.TryParse (s, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue)) result = intValue; else if (Int64.TryParse (s, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue)) result = longValue; else if (Decimal.TryParse (s, NumberStyles.Integer, CultureInfo.InvariantCulture, out decimalValue)) result = decimalValue; else if (Double.TryParse (s, NumberStyles.Integer, CultureInfo.InvariantCulture, out doubleValue)) result = doubleValue; else converted = false; break; case JsonType.FLOAT: if (Decimal.TryParse (s, NumberStyles.Float, CultureInfo.InvariantCulture, out decimalValue)) result = decimalValue; else if (Double.TryParse (s, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue)) result = doubleValue; else converted = false; break; case JsonType.TRUE: if (String.Compare (s.Trim (), "true", StringComparison.Ordinal) == 0) result = true; else converted = false; break; case JsonType.FALSE: if (String.Compare (s.Trim (), "false", StringComparison.Ordinal) == 0) result = false; else converted = false; break; case JsonType.NULL: if (String.Compare (s.Trim (), "null", StringComparison.Ordinal) != 0) converted = false; break; case JsonType.STRING: if (s.StartsWith ("/Date(", StringComparison.Ordinal) && s.EndsWith (")/", StringComparison.Ordinal)) { long javaScriptTicks = Convert.ToInt64 (s.Substring (6, s.Length - 8)); result = new DateTime ((javaScriptTicks * 10000) + JsonSerializer.InitialJavaScriptDateTicks, DateTimeKind.Utc); } else result = s; break; default: throw new InvalidOperationException (String.Format ("Internal error: unexpected JsonType ({0})", jsonType)); } if (!converted) throw new ArgumentException ("Invalid JSON primitive: " + s); buffer.Length = 0; return true; } bool ProcessCharacter (char ch) { int next_class, next_state; if (ch >= 128) next_class = C_ETC; else { next_class = ascii_class [ch]; if (next_class <= UNIVERSAL_ERROR) return false; } if (escaped) { escaped = false; RemoveLastCharFromBuffer (); switch (ch) { case 'b': buffer.Append ('\b'); break; case 'f': buffer.Append ('\f'); break; case 'n': buffer.Append ('\n'); break; case 'r': buffer.Append ('\r'); break; case 't': buffer.Append ('\t'); break; case '"': case '\\': case '/': buffer.Append (ch); break; case 'u': buffer.Append ("\\u"); break; default: return false; } } else if (jsonType != JsonType.NONE || !(next_class == C_SPACE || next_class == C_WHITE)) buffer.Append (ch); next_state = state_transition_table [state, next_class]; if (next_state >= 0) { state = next_state; return true; } object result; /* An action to perform */ switch (next_state) { case UC: /* Unicode character */ DecodeUnicodeChar (); state = ST; break; case EX: /* Escaped character */ escaped = true; state = ES; break; case MX: /* integer detected by minus */ jsonType = JsonType.INTEGER; state = MI; break; case PX: /* integer detected by plus */ jsonType = JsonType.INTEGER; state = MI; break; case ZX: /* integer detected by zero */ jsonType = JsonType.INTEGER; state = ZE; break; case IX: /* integer detected by 1-9 */ jsonType = JsonType.INTEGER; state = IN; break; case DE: /* floating point number detected by exponent*/ jsonType = JsonType.FLOAT; state = E1; break; case DF: /* floating point number detected by fraction */ jsonType = JsonType.FLOAT; state = FX; break; case SB: /* string begin " or ' */ buffer.Length = 0; quoteChar = ch; jsonType = JsonType.STRING; state = ST; break; case KB: /* unquoted key name begin */ jsonType = JsonType.STRING; state = UK; break; case UE: /* unquoted key name end ':' */ RemoveLastCharFromBuffer (); if (ParseBuffer (out result)) StoreKey (result); jsonType = JsonType.NONE; PopMode (JsonMode.KEY); PushMode (JsonMode.OBJECT); state = VA; buffer.Length = 0; break; case NU: /* n */ jsonType = JsonType.NULL; state = N1; break; case FA: /* f */ jsonType = JsonType.FALSE; state = F1; break; case TR: /* t */ jsonType = JsonType.TRUE; state = T1; break; case EO: /* empty } */ result = PopObject (true); if (result != null) StoreValue (result); PopMode (JsonMode.KEY); state = OK; break; case OE: /* } */ RemoveLastCharFromBuffer (); if (ParseBuffer (out result)) StoreValue (result); result = PopObject (true); if (result != null) StoreValue (result); PopMode (JsonMode.OBJECT); jsonType = JsonType.NONE; state = OK; break; case AE: /* ] */ RemoveLastCharFromBuffer (); if (ParseBuffer (out result)) StoreValue (result); PopMode (JsonMode.ARRAY); result = PopObject (true); if (result != null) StoreValue (result); jsonType = JsonType.NONE; state = OK; break; case OS: /* { */ RemoveLastCharFromBuffer (); CreateObject (); PushMode (JsonMode.KEY); state = OB; break; case AB: /* [ */ RemoveLastCharFromBuffer (); CreateArray (); PushMode (JsonMode.ARRAY); state = AR; break; case SE: /* string end " or ' */ if (ch == quoteChar) { RemoveLastCharFromBuffer (); switch (PeekMode ()) { case JsonMode.KEY: if (ParseBuffer (out result)) StoreKey (result); jsonType = JsonType.NONE; state = CO; buffer.Length = 0; break; case JsonMode.ARRAY: case JsonMode.OBJECT: if (ParseBuffer (out result)) StoreValue (result); jsonType = JsonType.NONE; state = OK; break; case JsonMode.NONE: /* A stand-alone string */ jsonType = JsonType.STRING; state = IV; /* the rest of input is invalid */ if (ParseBuffer (out result)) PushObject (result); break; default: throw new ArgumentException ("Syntax error: string in unexpected place."); } } break; case CM: /* , */ RemoveLastCharFromBuffer (); // With MS.AJAX, a comma resets the recursion depth recursionDepth = 0; bool doStore = ParseBuffer (out result); switch (PeekMode ()) { case JsonMode.OBJECT: if (doStore) StoreValue (result); PopMode (JsonMode.OBJECT); PushMode (JsonMode.KEY); jsonType = JsonType.NONE; state = KE; break; case JsonMode.ARRAY: jsonType = JsonType.NONE; state = VA; if (doStore) StoreValue (result); break; default: throw new ArgumentException ("Syntax error: unexpected comma."); } break; case CA: /* : */ RemoveLastCharFromBuffer (); // With MS.AJAX a colon increases recursion depth if (++recursionDepth >= recursionLimit) throw new ArgumentException ("Recursion limit has been reached on parsing input."); PopMode (JsonMode.KEY); PushMode (JsonMode.OBJECT); state = VA; break; case IF: /* Infinity */ case NN: /* NaN */ jsonType = JsonType.FLOAT; switch (PeekMode ()) { case JsonMode.ARRAY: case JsonMode.OBJECT: if (ParseBuffer (out result)) StoreValue (result); jsonType = JsonType.NONE; state = OK; break; case JsonMode.NONE: /* A stand-alone NaN/Infinity */ jsonType = JsonType.FLOAT; state = IV; /* the rest of input is invalid */ if (ParseBuffer (out result)) PushObject (result); break; default: throw new ArgumentException ("Syntax error: misplaced NaN/Infinity."); } buffer.Length = 0; break; default: throw new ArgumentException (GetModeMessage (PeekMode ())); } return true; } void CreateArray () { var arr = new ArrayList (); PushObject (arr); } void CreateObject () { var dict = new Dictionary (); PushObject (dict); } void StoreKey (object o) { string key = o as string; if (key != null) key = key.Trim (); if (String.IsNullOrEmpty (key)) throw new InvalidOperationException ("Internal error: key is null, empty or not a string."); currentKey.Push (key); Dictionary dict = PeekObject () as Dictionary ; if (dict == null) throw new InvalidOperationException ("Internal error: current object is not a dictionary."); /* MS AJAX.NET silently overwrites existing currentKey value */ dict [key] = null; } void StoreValue (object o) { Dictionary dict = PeekObject () as Dictionary ; if (dict == null) { ArrayList arr = PeekObject () as ArrayList; if (arr == null) throw new InvalidOperationException ("Internal error: current object is not a dictionary or an array."); arr.Add (o); return; } string key; if (currentKey.Count == 0) key = null; else key = currentKey.Pop (); if (String.IsNullOrEmpty (key)) throw new InvalidOperationException ("Internal error: object is a dictionary, but no key present."); dict [key] = o; } } }