#if false
#define	DEVELOPING

using System;
using System.Data;

using System.Text;
using System.Globalization;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Collections.Specialized;

using System.Runtime.InteropServices;

using System.Diagnostics;

using System.Text.RegularExpressions;
using Westwind.Tools;
using System.Collections.Generic;


#if DEVELOPING
	#pragma warning disable 219
	#pragma warning disable 162
	#pragma warning disable 414
#endif


/*

	To Do:
	
		generic object members		
		

*/


namespace Westwind.Web.Controls {


	///////////////////////////////////////////////////////////////////////////
	
	public class ParseStringReader {

		private string _s;
		private int _pos;
		private int _length;

		///////////////////////////////////////////////////////////////////////////
		
		public string	GetString		{ get { return _s; } }
		public int		GetPos			{ get { return _pos; } }
		
		///////////////////////////////////////////////////////////////////////////
	
		public int Peek()
		{
			if( null == _s ) {
				ReaderClosed();
			}
			if (_pos == _length) return -1;
			return _s[_pos];
		}
	
		///////////////////////////////////////////////////////////////////////////
	
		public int Read() {
			//
			// Reads the next character from the underlying string. The returned value
			// is -1 if no further characters are available.
			//
			if (_s == null)
				ReaderClosed();
			if (_pos == _length) return -1;
			return _s[_pos++];
		}
	
		///////////////////////////////////////////////////////////////////////////
		
		public int FwdSearch( int offset, Predicate<char> fun )
		{
			// ******
			offset += _pos;
			if( offset < 0 || offset >= _length ) {
				throw new Exception( "the offset parameter combines to form an out of range index" );
			}
			
			// ******
			for( int i = offset; i < _length; i++ ) {
				if( fun(_s[i]) ) {
					return i;
				}
			}

			// ******
			return -1;
		}
	
		///////////////////////////////////////////////////////////////////////////
	
		protected void ReaderClosed()
		{
			throw new Exception( "ParseStringReader is closed" );
		}
	

		///////////////////////////////////////////////////////////////////////////
	
		public void Close()
		{
			_s = null;
			_length = 0;
			_pos = 0;
		}
		
		///////////////////////////////////////////////////////////////////////////
	
		public ParseStringReader( string str )
		{
			// ******
			if( null == str ) {
				throw new ArgumentNullException("str");
			}
			
			// ******
			_s = str;
			_length = _s.Length;
		}
	
	}


	///////////////////////////////////////////////////////////////////////////
	
	public class ParseReader {
	
		public const char	NO_CHAR				= '\0';
		public const char	WHITE_SPACE		= ' ';		// represents all white-space

		public const char	OPEN_OBJECT		= '{';
		public const char	CLOSE_OBJECT	= '}';
	
		public const char	OPEN_ARRAY		= '[';
		public const char	CLOSE_ARRAY		= ']';
		
		public const char	COMMA					= ',';
		public const char	QUOTE					= '"';
		public const char	COLON					= ':';

		private ParseStringReader	reader = null;
		
		private char	pushedBack = NO_CHAR;
		
		private bool	enforceIdentifierFormat	= true;
		private bool	reqStringDataQuotes			= true;
		private char	strDataQuoteChar				= '"';
		private bool	allowEmbededQuotes			= false;

		///////////////////////////////////////////////////////////////////////////

		public bool	EnforceIdentifierFormat { get { return enforceIdentifierFormat; } set { enforceIdentifierFormat = value; } }
		public bool	RequireStringDataQuotes { get { return reqStringDataQuotes; } set { reqStringDataQuotes = value; } }
		public char	StringDataQuoteChar 		{ get { return strDataQuoteChar; } set { strDataQuoteChar = value; } }
		public bool	AllowEmbededQuotes			{ get { return allowEmbededQuotes; } set { allowEmbededQuotes = value; } }

		///////////////////////////////////////////////////////////////////////////
		
		public bool IsOpenObject( char ch )
		{
			return OPEN_OBJECT == ch;
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public bool IsCloseObject( char ch )
		{
			return CLOSE_OBJECT == ch;
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public bool IsOpenArray( char ch )
		{
			return OPEN_ARRAY == ch;
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public bool IsCloseArray( char ch )
		{
			return CLOSE_ARRAY == ch;
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public bool IsComma( char ch )
		{
			return COMMA == ch;
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public bool IsJSONNull( string word )
		{
			return "null" == word.ToLower();
		}
		
		
		///////////////////////////////////////////////////////////////////////////

		public char Peek()
		{
			// ******
			int val = reader.Peek();
			if( val < 0 ) {
				return NO_CHAR;
			}
			
			// ******
			return (char) val;
		}
		

		///////////////////////////////////////////////////////////////////////////

		public bool WhiteSpace()
		{
			// ******
			while( true ) {
				char ch = Peek();
				if( NO_CHAR == ch ) {
					return false;
				}
				
				if( Char.IsWhiteSpace(ch) ) {
					reader.Read();
				}
				else {
					return true;
				}
			}
		}
		

		///////////////////////////////////////////////////////////////////////////
		
		private char GetNextChar( bool skipWhiteSpace )
		{
			// ******
			while( true ) {
				int val = reader.Read();
				if( val < 0 ) {
					return NO_CHAR;
				}
				if( skipWhiteSpace && Char.IsWhiteSpace((char) val) ) {
					continue;
				}
				
				// ******
				return (char) val;
			}
		}
		

		///////////////////////////////////////////////////////////////////////////
		
		public char NextCharSkipSpace()
		{
			return GetNextChar( true );
		}
		

		///////////////////////////////////////////////////////////////////////////
		
		public char NextChar()
		{
			return GetNextChar( false );
		}
		

		///////////////////////////////////////////////////////////////////////////

		static string valueTerminatorChar = "}],";

		public char NextValue( Type valueType, out string value )
		{
		StringBuilder sb = new StringBuilder();
		bool isString = valueType == typeof(string);
		
			// ******
			WhiteSpace();
			
			if( isString && reqStringDataQuotes ) {
				if( strDataQuoteChar != Peek() ) {
					//
					// special case to detect 'null' in place of opening quote
					//
					if( IsJSONNull(Word()) ) {
						value = "null";
						WhiteSpace();
						return Peek();
					}
					
					// ******
					throw new InvalidOperationException( "NextValue(): expecting opening quote for string value." );
				}
				sb.Append( NextChar() );
			}
			
			// ******
			char lastChar = '|';
			char nextChar = '|';
			
			while( true ) {
				lastChar = nextChar;
				nextChar = Peek();
			
				// ******
				if( NO_CHAR == nextChar ) {
					//
					// end of string
					//
					break;
				}

				// ******
				if( isString && reqStringDataQuotes ) {
					if( strDataQuoteChar == nextChar && '\\' != lastChar ) {

						///////////////////////////////////////////////////////////////////
												
						Predicate<string> 
						isTrulyEnd = delegate ( string termChars )
						{
							char ch = ' ';
							//
							// locates the first non-white-space char following 'strDataQuoteChar'
							//
							int pos = reader.FwdSearch( 1, v => {	if( Char.IsWhiteSpace(v) ){return false;} ch=v; return true; } );
							//
							// it is truly the end if pos < 0 (end of string) or ch in termChars
							//
							return pos < 0 || termChars.IndexOf(ch) >= 0;
						};
							
						if( ! allowEmbededQuotes || isTrulyEnd(valueTerminatorChar) ) {
							//
							// end of string
							//
							sb.Append( NextChar() );
							break;
						}
					}
				}
				else if( valueTerminatorChar.IndexOf(nextChar) >= 0 ) {
					//
					// end of number, boolean, date or "null"
					//
					break;
				}
				
				// ******
				sb.Append( NextChar() );
			}

			//
			// removes any trailing white space
			//
			string valueStr = sb.ToString().Trim();
			
			// ******
			if( ! isString ) {
			
				if( typeof(bool) == valueType ) {
					if( "1" == valueStr || "on" == valueStr ) {
						valueStr = "true";
					}
					else if( "0" == valueStr || "off" == valueStr ) {
						valueStr = "false";
					}
				}
			}

			// ******
			value = valueStr;
			
			// ******
			WhiteSpace();
			return Peek();
		}
		
		
		///////////////////////////////////////////////////////////////////////////

		public bool NextValueOrNull( Type valueType, out string value )
		{
			// ******
			NextValue( valueType, out value );
			return null != value && "null" == value.ToLower();
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		protected char GetIdentifier( bool shouldBeQuoted, out string id )
		{
		StringBuilder	sb = new StringBuilder();
		
			// ******
			char	nextChar = NextCharSkipSpace();

			if( shouldBeQuoted ) {
				if( QUOTE != nextChar ) {
					throw new InvalidOperationException( "GetIdentifier(): quote expected before identifier." );
				}
				nextChar = NextChar();
			}

			// ******
			while( true ) {
				if( NO_CHAR == nextChar ) {
					throw new InvalidOperationException( "GetIdentifier(): end of input while parsing identifier." );
				}

				if( Char.IsWhiteSpace(nextChar) || (shouldBeQuoted && QUOTE == nextChar) ) {
					//
					// done
					//
					break;
				}
				
				if( enforceIdentifierFormat ) {
					//
					// [_a-zA-Z][_a-zA-Z0-9]*
					//
					if( 0 == sb.Length && (! Char.IsLetter(nextChar) && '_' != nextChar) ) {
						throw new InvalidOperationException( String.Format("GetIdentifier(): invalid character \"{0}\" at start of identifier.", nextChar) );
					}
					else if( ! Char.IsLetterOrDigit(nextChar) && '_' != nextChar ) {
						throw new InvalidOperationException( String.Format("GetIdentifier(): invalid character \"{0}\" in identifier.", nextChar) );
					}
				}
				
				// ******
				sb.Append( nextChar );
				nextChar = NextChar();
			}
			
			// ******
			if( shouldBeQuoted && QUOTE != nextChar ) {
				throw new InvalidOperationException( "GetIdentifier(): closing quote missing in identifier." );
			}

			// ******
			if( 0 == sb.Length ) {
				throw new InvalidOperationException( "GetIdentifier(): expected identifier, found empty string." );
			}
			
			// ******
			id = sb.ToString();
			
			// ******
			WhiteSpace();
			return Peek();
		}
		

		///////////////////////////////////////////////////////////////////////////
		
		protected string Word()
		{
		StringBuilder	sb = new StringBuilder();
		
			// ******
			WhiteSpace();
			
			char	peekChar = Peek();
			while( Char.IsLetterOrDigit(peekChar) || '_' == peekChar ) {
				sb.Append( NextChar() );
				peekChar = Peek();
			}

			// ******
			return sb.ToString();
		}
		

		///////////////////////////////////////////////////////////////////////////
		
		public bool Initialize( ParseStringReader reader )
		{
			this.reader = reader;
			return true;
		}
		
		
	};	// ParseReader



	///////////////////////////////////////////////////////////////////////////

	public class MemberInfoInst {
	
		object				parent = null;
		MemberInfo[]	mi = null;
		Type					memberType = typeof(object);
		bool					memberFound = false;
		
		///////////////////////////////////////////////////////////////////////////

		public bool			MemberFound		{ get { return memberFound; } }
		public TypeCode	GetTypeCode 	{ get { return Type.GetTypeCode(memberType); } }
		public bool			IsPrimitive 	{ get { return memberType.IsPrimitive; } }
		public Type			MemberType		{ get { return memberType; } }
		
		///////////////////////////////////////////////////////////////////////////

		public void	SetValue( object value )
		{
			// ******
			if( ! memberFound ) {
				return;
			}
			
			// ******
			if( MemberTypes.Field == mi[0].MemberType ) {
				FieldInfo FInfo = mi[0] as FieldInfo;
				FInfo.SetValue( parent, value );
			}
			else {
				PropertyInfo PInfo = mi[0] as PropertyInfo;
				PInfo.SetValue( parent, value, null );
			}
		}
		
		
		///////////////////////////////////////////////////////////////////////////
		
		public MemberInfoInst( object parent, string id, bool ignoreCase )
		{
			// ******
			this.parent = parent;

			// ******
			BindingFlags flags =	BindingFlags.Instance
														| BindingFlags.GetField
														| BindingFlags.GetProperty
														| BindingFlags.Public;
			if( ignoreCase ) {
				flags |= BindingFlags.IgnoreCase;
			}
			
			// ******
			mi = parent.GetType().GetMember( id, flags );
			if( mi == null || mi.Length < 1 ) {
				mi = null;
				memberFound = false;
				return;
			}
			
			// ******
			memberFound = true;
			
			// ******
			if( mi[ 0 ].MemberType == MemberTypes.Field ) {
				memberType = ((FieldInfo) mi[ 0 ]).FieldType;
			}
			else {
				memberType = ((PropertyInfo) mi[ 0 ]).PropertyType;
			}
		}

	};	// MemberInfoInst
		

	///////////////////////////////////////////////////////////////////////////

	public partial class JSONDeserializer : ParseReader {
		
		// ******
		bool	ignoreUnknownMembers = false;
		
		/*
			
			The handling of JSON 'null' values:
			
				If null is parsed for assignment to a non reference type (primitives and decimals), DateTime or
				a struct member (which we don't currently support) an exception is thrown.
				
				For an array, when 'allowNullArrayItems' is false the null values are ignored, when
				'allowNullArrayItems' is true a null value is added to the array. As noted above,
				null values for primitives (et.al.) are trapped and an excediton is thrown - e.g. an
				array of ints can't have a null entry while an array of strings can.
				
				For an object, when 'allowNullObjectItems is false the null values are ignored, when
				'allowNullObjectItems' is true a null value is assigned to the object property. Again,
				as noted above null values for primitives cause an exception.
				
		*/
		bool	allowNullArrayItems = false;
		bool	allowNullObjectItems = false;
		bool	allowNullValueItems = false;
		

		/// <summary>
		/// The JavaScript base date on which all date time values
		/// in JavaScript are based. Offsets are given in milliseconds
		/// (ie. new Date(99990123) where the number is the offset in ms
		/// from the base date)
		/// </summary>
		
		static DateTime DAT_BASEDATE = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

		
		///////////////////////////////////////////////////////////////////////////

		public bool	AllowNullArrayItems		{ get { return allowNullArrayItems; } set { allowNullArrayItems = value; } }
		public bool	AllowNullObjectItems	{ get { return allowNullObjectItems; } set { allowNullObjectItems = value; } }
		public bool	AllowNullValueItems		{ get { return allowNullValueItems; } set { allowNullValueItems = value; } }

		///////////////////////////////////////////////////////////////////////////
		
		protected bool IsArray( Type valueType )
		{
			return valueType.IsArray || valueType.GetInterface("IList") != null;
		}
		

		///////////////////////////////////////////////////////////////////////////

		private Type GetArrayType( Type ArrayType )
		{
			// ******
			if( ArrayType.IsArray ) {
				return ArrayType.GetElementType();
			}

			// ******
			if( ArrayType == typeof(DataTable) ) {
				return typeof(DataRow);
			}

			// ******
			if (ArrayType.GetInterface("IList") != null) {
				MethodInfo Method = ArrayType.GetMethod( "Add" );
				ParameterInfo Parameter = Method.GetParameters()[0];
				Type ResultType = Parameter.ParameterType;
				return ResultType;
			}

			// ******
			return null;
		}


		/////////////////////////////////////////////////////////////////////////////
	
		private string UnicodeEscapeMatchEvaluator( Match match )
		{
			// *** last 4 digits are hex value
			string hex = match.Value.Substring(2);
			char val = (char)ushort.Parse(hex, System.Globalization.NumberStyles.HexNumber);
			return val.ToString();
		}

		
		///////////////////////////////////////////////////////////////////////////

		private object StringFromString( string value )
		{
            if (value == "null")
                return null;

            value = value.Replace(@"\r", "\r");
            value = value.Replace(@"\n", "\n");
            value = value.Replace(@"\\", "\\");
            value = value.Replace(@"\""", "\"");
            value = value.Replace(@"\t", "\t");
            value = value.Replace(@"\b", "\b");
            value = value.Replace(@"\f", "\f");

            if (value.Contains("\\u"))
            {
                value = Regex.Replace(value, @"\\u....",
                              new MatchEvaluator(this.UnicodeEscapeMatchEvaluator));
            }

            // *** strip off leading and trailing quote chars
            string val = value.Substring(1, value.Length - 2);
            
            return val;

            //// ******
            //value = value.Replace(@"\r", "\r");
            //value = value.Replace(@"\n", "\n");
            //value = value.Replace(@"\\", "\\");
            //value = value.Replace(@"\""", "\"");
            //value = value.Replace(@"\t", "\t");
            //value = value.Replace(@"\b", "\b");
            //value = value.Replace(@"\f", "\f");

            //// ******
            //if( value.Contains("\\u") ) {
            //    value = Regex.Replace(value, @"\\u....", new MatchEvaluator(this.UnicodeEscapeMatchEvaluator) );
            //}

            //// *** strip off leading and trailing quote chars
            //if( value.Length > 0 && '"' == value[0] ) {
            //    value = value.Remove( 0, 1 );
            //}
            //if( value.Length > 0 && '"' == value[value.Length - 1] ) {
            //    value = value.Remove( value.Length - 1 );
            //}
			
            //// ******
            //return value;
		}
		
		
		///////////////////////////////////////////////////////////////////////////

		private object NumberFromString( string value, Type numType )
		{
		object result = null;
		CultureInfo culture = CultureInfo.CurrentCulture;

			// ******
			if( numType == typeof(int) ) {
				result = int.Parse(value, NumberStyles.Integer, culture.NumberFormat);
			}
			else if( numType == typeof(byte) ) {
				result = Convert.ToByte(value);
			}
			else if( numType == typeof(decimal) ) {
				result = Decimal.Parse(value, NumberStyles.Any, culture.NumberFormat);
			}
			else if( numType == typeof(double) ) {
				result = Double.Parse(value, NumberStyles.Any, culture.NumberFormat);
			}
			else if( numType == typeof(bool) ) {
				//
				// yea
				//
				result = 0 == String.Compare( "true", value, true );
			}
			else {
				throw new InvalidOperationException( "NumberFromString(): unknown or unsupported number type." );
			}

			// ******
			return result;
		}
	
	
		///////////////////////////////////////////////////////////////////////////

		private DateTime DateFromString( string dateStr )
		{
			// ******
			if( 0 == dateStr.Length ) {
                return DateTime.MinValue;
			}

			// *** Strip off string wrapper since dates are embedded as strings
			// *** This should only be the case if a raw Date value is parsed
			// *** Inside of objects the value will be treated like a result string
			// *** and so lack the quotes.

			// ******
			//
			// remove quotes on quoted date string: "\/Date(...)\/"
			//
			dateStr = dateStr.Trim('\"');

			try {
				//
				// ASP.NET Ajax date serialization format (\/Date(ticks)\/)
				//
				if( dateStr.StartsWith(@"\/Date(", StringComparison.Ordinal) ) {
					dateStr = wwUtils.ExtractString(dateStr, @"\/Date(", @")\/");
					double ticks = 0;
					
					if( double.TryParse(dateStr, out ticks) ) {
						if( ticks == 0 ) {
							return DateTime.MinValue;
						}

						return DAT_BASEDATE.AddMilliseconds(ticks).ToLocalTime();
					}
					
					return DateTime.MinValue;
				}

				// ******
				//
				// Parse out what's between the date parens
				//
				dateStr = wwUtils.ExtractString(dateStr, "new Date(", ")");

				if( string.IsNullOrEmpty(dateStr) ) {
					//
					// new Date() should never happen - error
					//
					return DateTime.MinValue;
				}

				// ******
				//
				// 1999,12,1,12,00,59
				//
				if( dateStr.Contains(",") ) {
					string[] DateParts = dateStr.Split(',');

					return new DateTime(	int.Parse(DateParts[0]),
																int.Parse(DateParts[1]) + 1,
																int.Parse(DateParts[2]),
																int.Parse(DateParts[3]),
																int.Parse(DateParts[4]),
																int.Parse(DateParts[5]),
																DateTimeKind.Utc
														).ToLocalTime();
				}
				
				// ******
				//
				// JavaScript .GetTime() style date based on milliseconds since 1970
				//
				if( ! dateStr.Contains("-") && ! dateStr.Contains("/") ) {
					double ticks = 0;
					
					if( double.TryParse(dateStr, out ticks) ) {
						if( ticks == 0 ) {
							return DateTime.MinValue;
						}
						
						return DAT_BASEDATE.AddMilliseconds(ticks).ToLocalTime();
					}
	
					return DateTime.MinValue;
				}

				// ******
				//
				// *** Try to parse with UTC round trip format or invariant auto-detection
				//
				dateStr = dateStr.Replace( "\"", "" );
				return DateTime.Parse( dateStr, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.RoundtripKind ).ToLocalTime();
			}
			catch {
				//return DateTime.MinValue;
				throw new InvalidOperationException( "DateFromString(): unknown date format." );
			}

		}
	

		///////////////////////////////////////////////////////////////////////////

		private object ParseValue( Type valueType )
		{
		object	result = null;
		string	valueString = null;
		
			// ******
			WhiteSpace();
			char peekChar	= Peek();
			
			// ******
			TypeCode	typeCode = Type.GetTypeCode( valueType );

			if( valueType.IsPrimitive || TypeCode.Decimal == typeCode ) {
				//
				// The primitive types are:
				//
				//		Boolean, Byte, SByte, Int16, UInt16, 
				//		Int32, UInt32, Int64, UInt64, IntPtr, 
				//		Char, Double, and Single
				//
				if( NextValueOrNull(valueType, out valueString) ) {
					//
					// null, note: not dealing with nullable types
					//
					throw new InvalidOperationException( "ParseValue(): can't assign null to a value type." );
				}
				result = NumberFromString( valueString, valueType );
			}
			else if( TypeCode.String == typeCode ) {
				if( ! NextValueOrNull(valueType, out valueString) ) {
					result = StringFromString( valueString );
				}
			}
			else if( TypeCode.DateTime == typeCode ) {
				if( NextValueOrNull(valueType, out valueString) ) {
					throw new InvalidOperationException( "ParseValue(): can't assign null to a DateTime." );
				}
				result = DateFromString( valueString );
			}
			else if( null == valueType.BaseType ) {
				//
				// an 'object', e.g. ->  object myObjectClassMember;
				//
				throw new InvalidOperationException( "ParseValue(): generic 'object' members are not currenty supported." );
			}
			else if( IsArray(valueType) ) {
				//
				// an array
				//
				if( IsOpenArray(peekChar) ) {
					result = ParseArray( valueType );
				}
				else {
					if( ! IsJSONNull(Word()) ) {
						throw new InvalidOperationException( "ParseValue(): expecting array value." );
					}
					// else return null
				}
			}
			else if( valueType.IsClass && TypeCode.Object == typeCode ) {
				//
				// an object defined with "class" (not a struct!)
				//
				if( IsOpenObject(peekChar) ) {
					result = ParseObject( valueType );
				}
				else {
					if( ! IsJSONNull(Word()) ) {
						throw new InvalidOperationException( "ParseValue(): expecting object value." );
					}
					// else return null
				}
			}
			else {
				throw new InvalidOperationException( String.Format("ParseValue(): unknown type \"{0}\".", valueType.Name) );
			}

			// ******
			return result;
		}


		///////////////////////////////////////////////////////////////////////////

		protected bool AllowAssignment( object value, Type valueType, bool allowObjOrArrayNulls )
		{
			// ******
			if( null == value && (valueType.IsPrimitive || typeof(DateTime) == valueType) ) {
				//
				// should have been caught in ParseValue()
				//
				throw new InvalidOperationException( "AllowAssignment(): error should have been caught in ParseValue()" );
			}
			else if( null != value || allowObjOrArrayNulls ) {
				//
				// value or null (if NOT a value type)
				//
				return true;
			}
		
			return false;
		}


		///////////////////////////////////////////////////////////////////////////
		
		public object ParseObject( Type objectType )
		{
		object result = null;

			// ******
			result = Activator.CreateInstance( objectType );

			// ******
			WhiteSpace();
			if( OPEN_OBJECT == Peek() ) {
				NextChar();
			}

			// ******
			while( true ) {
				//
				// { "identifier" : value, "array" : [...], "object" : {...} }
				//
				string	id = null;
				object	value = null;
				char	peekChar;
				
				// ******
				WhiteSpace();
				if( CLOSE_OBJECT == Peek() ) {
					NextChar();
					return result;
				}
				
				// ******
				//
				// "identifier" :
				//	
				peekChar = GetIdentifier( true, out id );
				if( COLON != peekChar ) {
					//
					// oops
					//
					throw new InvalidOperationException( "ParseObject(): expected ':' following identifier." );
				}
				NextChar();	// skipping COLON
				WhiteSpace();
				peekChar = Peek();
				
				// ******
				MemberInfoInst mii = new MemberInfoInst( result, id, true /*ignoreCase*/ );
				
				if( ! mii.MemberFound ) {
					//
					// the JSON object has a member whose name we can not find in the 'result' object,
					// we can try to skip the value if its not another object or an array, the default is
					// to terminate parsing
					//
					if( ! ignoreUnknownMembers || IsOpenObject(peekChar) || IsOpenArray(peekChar) ) {
						//
						// skipping arrays or objects is fraught with possible errors
						//
						throw new InvalidOperationException( String.Format("ParseObject(): unknown member \"{0}\". in {1}", id, objectType.FullName) );
					}
					string	valueString = null;
					NextValue( typeof(int), out valueString );
					continue;
				}
				
				// ******
				value = ParseValue( mii.MemberType );
				
				// ******
				WhiteSpace();
				peekChar = Peek();
				
				if( IsComma(peekChar) ) {
					NextChar();
				}
				else if( IsCloseObject(peekChar) ) {
					//
					// this is ok to, but don't advance
					//
				}
				else {
					//
					// nothing else is ok
					//
					throw new InvalidOperationException( "ParseObject(): comma (,) expected while parsing JSON object" );
				}

				// ******
				if( AllowAssignment(value, mii.MemberType, allowNullObjectItems) ) {
					mii.SetValue( value );
				}
			}
			
			// ******
			throw new InvalidOperationException( "ParseObject(): the object did not close." );
			//return null;
		}

	
		///////////////////////////////////////////////////////////////////////////
		
		public object ParseArray( Type arrayType )
		{
		Type valueType = GetArrayType( arrayType );
		ArrayList items = new ArrayList();
		
			// ******
			if( ! IsArray(arrayType) ) {
				//
				// gota be an array
				//
				throw new InvalidOperationException( String.Format("ParseArray(): type \"{0}\" is not an array.", arrayType.Name) );
			}
			
			if( ! arrayType.IsArray && null == arrayType.GetInterface("IList") ) {
				//
				// and it has to be one that we know how to deal with
				//
				throw new InvalidOperationException( "ParseArray(): arrayType is not an Array or an IList" );
			}
			
			// ******
			TypeCode	typeCode = Type.GetTypeCode( valueType );
			bool			isPrimitive = valueType.IsPrimitive;
			
			// ******
			WhiteSpace();
			if( OPEN_ARRAY == Peek() ) {
				NextChar();
			}
			
			// ******
			while( true ) {
				//
				// [ "string", 123.01, [...], {...}, true, false, null }
				//
				string	valueString = null;
				object	value = null;
				
				WhiteSpace();
				char peekChar = Peek();

				// ******
				if( CLOSE_ARRAY == peekChar ) {
					//
					// end of array
					//
					NextChar();
					
					if( arrayType.IsArray ) {
						Array ElementArray = Activator.CreateInstance(arrayType, items.Count) as Array;
						for( int i = 0; i < items.Count; i++ ) {
							ElementArray.SetValue( items[i], i );
						}
						return ElementArray;
					}
					else if( arrayType.GetInterface("IList") != null ) {
						IList Col = Activator.CreateInstance(arrayType) as IList;
						foreach( object Item in items ) {
							if( null == Item ) {
								//
								// can't have 'null's here
								//
								continue;
							}
							Col.Add(Item);
						}
						return Col;
					}
				
					throw new InvalidOperationException( "ParseArray(): should never happen error (1)" );
				}

				// ******
				value = ParseValue( valueType );
				
				// ******
				WhiteSpace();
				peekChar = Peek();
				
				if( IsComma(peekChar) ) {
					NextChar();
				}
				else if( IsCloseArray(peekChar) ) {
					//
					// this is ok too, but we don't eat it
					//
				}
				else {
					//
					// but nothing else is
					//
					throw new InvalidOperationException( "ParseArray(): comma (,) expected while parsing JSON array" );
				}

				if( AllowAssignment(value, valueType, allowNullArrayItems) ) {
					items.Add( value );
				}
			}
			
			// ******
			throw new InvalidOperationException( "ParseArray(): the array did not close." );
			//return null;
		}
	
	
		///////////////////////////////////////////////////////////////////////////
		//
		// Main entry to parser
		//
		///////////////////////////////////////////////////////////////////////////

		public object Parse( string JSONText, Type elementType )
		{
		object	result = null;
		
			// ******
			Initialize( new ParseStringReader(JSONText) );
			
			RequireStringDataQuotes = true;			
			//AllowEmbededQuotes = true;
			
			try {
				result = ParseValue( elementType );
			}
			catch ( InvalidOperationException e ) {
				throw e;
			}
			
			
			// ******
			return result;
		}

	};		// JSONDeserializer


/////////////////////////////////////////////////////////////////////////////
};
/////////////////////////////////////////////////////////////////////////////
#endif