e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
888 lines
28 KiB
C#
888 lines
28 KiB
C#
//
|
|
// JsonDeserializer.cs
|
|
//
|
|
// Author:
|
|
// Marek Habersack <grendel@twistedcode.net>
|
|
//
|
|
// (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 <JsonMode> modes;
|
|
Stack <object> returnValue;
|
|
JsonType jsonType;
|
|
|
|
bool escaped;
|
|
int state;
|
|
Stack <string> 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 <JsonMode> ();
|
|
this.currentKey = new Stack <string> ();
|
|
this.returnValue = new Stack <object> ();
|
|
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 <string, object>) {
|
|
Console.WriteLine (indent + "{");
|
|
foreach (KeyValuePair <string, object> kvp in (Dictionary <string, object>)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 <string, object> ();
|
|
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 <string, object> dict = PeekObject () as Dictionary <string, object>;
|
|
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 <string, object> dict = PeekObject () as Dictionary <string, object>;
|
|
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;
|
|
}
|
|
}
|
|
}
|