2021-03-29 14:54:12 +08:00
#region License
// Copyright (c) 2007 James Newton-King
/ /
// 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.
# endregion
using System ;
using System.Runtime.CompilerServices ;
using System.IO ;
using System.Globalization ;
#if HAVE_BIG_INTEGER
using System.Numerics ;
# endif
using LC.Newtonsoft.Json.Utilities ;
namespace LC.Newtonsoft.Json
{
internal enum ReadType
{
Read ,
ReadAsInt32 ,
ReadAsInt64 ,
ReadAsBytes ,
ReadAsString ,
ReadAsDecimal ,
ReadAsDateTime ,
#if HAVE_DATE_TIME_OFFSET
ReadAsDateTimeOffset ,
# endif
ReadAsDouble ,
ReadAsBoolean
}
/// <summary>
/// Represents a reader that provides fast, non-cached, forward-only access to JSON text data.
/// </summary>
public partial class JsonTextReader : JsonReader , IJsonLineInfo
{
private const char UnicodeReplacementChar = ' \ uFFFD ' ;
#if HAVE_BIG_INTEGER
private const int MaximumJavascriptIntegerCharacterLength = 380 ;
# endif
#if DEBUG
internal int LargeBufferLength { get ; set ; } = int . MaxValue / 2 ;
# else
private const int LargeBufferLength = int . MaxValue / 2 ;
# endif
private readonly TextReader _reader ;
2021-03-30 10:54:25 +08:00
private char [ ] _chars ;
2021-03-29 14:54:12 +08:00
private int _charsUsed ;
private int _charPos ;
private int _lineStartPos ;
private int _lineNumber ;
private bool _isEndOfFile ;
private StringBuffer _stringBuffer ;
private StringReference _stringReference ;
2021-03-30 10:54:25 +08:00
private IArrayPool < char > _arrayPool ;
2021-03-29 14:54:12 +08:00
/// <summary>
/// Initializes a new instance of the <see cref="JsonTextReader"/> class with the specified <see cref="TextReader"/>.
/// </summary>
/// <param name="reader">The <see cref="TextReader"/> containing the JSON data to read.</param>
public JsonTextReader ( TextReader reader )
{
if ( reader = = null )
{
throw new ArgumentNullException ( nameof ( reader ) ) ;
}
_reader = reader ;
_lineNumber = 1 ;
#if HAVE_ASYNC
_safeAsync = GetType ( ) = = typeof ( JsonTextReader ) ;
# endif
}
#if DEBUG
2021-03-30 10:54:25 +08:00
internal char [ ] CharBuffer
2021-03-29 14:54:12 +08:00
{
get = > _chars ;
set = > _chars = value ;
}
internal int CharPos = > _charPos ;
# endif
/// <summary>
/// Gets or sets the reader's property name table.
/// </summary>
2021-03-30 10:54:25 +08:00
public JsonNameTable PropertyNameTable { get ; set ; }
2021-03-29 14:54:12 +08:00
/// <summary>
/// Gets or sets the reader's character buffer pool.
/// </summary>
2021-03-30 10:54:25 +08:00
public IArrayPool < char > ArrayPool
2021-03-29 14:54:12 +08:00
{
get = > _arrayPool ;
set
{
if ( value = = null )
{
throw new ArgumentNullException ( nameof ( value ) ) ;
}
_arrayPool = value ;
}
}
private void EnsureBufferNotEmpty ( )
{
if ( _stringBuffer . IsEmpty )
{
_stringBuffer = new StringBuffer ( _arrayPool , 1024 ) ;
}
}
private void SetNewLine ( bool hasNextChar )
{
if ( hasNextChar & & _chars [ _charPos ] = = StringUtils . LineFeed )
{
_charPos + + ;
}
OnNewLine ( _charPos ) ;
}
private void OnNewLine ( int pos )
{
_lineNumber + + ;
_lineStartPos = pos ;
}
private void ParseString ( char quote , ReadType readType )
{
_charPos + + ;
ShiftBufferIfNeeded ( ) ;
ReadStringIntoBuffer ( quote ) ;
ParseReadString ( quote , readType ) ;
}
private void ParseReadString ( char quote , ReadType readType )
{
SetPostValueState ( true ) ;
switch ( readType )
{
case ReadType . ReadAsBytes :
Guid g ;
byte [ ] data ;
if ( _stringReference . Length = = 0 )
{
data = CollectionUtils . ArrayEmpty < byte > ( ) ;
}
else if ( _stringReference . Length = = 36 & & ConvertUtils . TryConvertGuid ( _stringReference . ToString ( ) , out g ) )
{
data = g . ToByteArray ( ) ;
}
else
{
data = Convert . FromBase64CharArray ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length ) ;
}
SetToken ( JsonToken . Bytes , data , false ) ;
break ;
case ReadType . ReadAsString :
string text = _stringReference . ToString ( ) ;
SetToken ( JsonToken . String , text , false ) ;
_quoteChar = quote ;
break ;
case ReadType . ReadAsInt32 :
case ReadType . ReadAsDecimal :
case ReadType . ReadAsBoolean :
// caller will convert result
break ;
default :
if ( _dateParseHandling ! = DateParseHandling . None )
{
DateParseHandling dateParseHandling ;
if ( readType = = ReadType . ReadAsDateTime )
{
dateParseHandling = DateParseHandling . DateTime ;
}
#if HAVE_DATE_TIME_OFFSET
else if ( readType = = ReadType . ReadAsDateTimeOffset )
{
dateParseHandling = DateParseHandling . DateTimeOffset ;
}
# endif
else
{
dateParseHandling = _dateParseHandling ;
}
if ( dateParseHandling = = DateParseHandling . DateTime )
{
if ( DateTimeUtils . TryParseDateTime ( _stringReference , DateTimeZoneHandling , DateFormatString , Culture , out DateTime dt ) )
{
SetToken ( JsonToken . Date , dt , false ) ;
return ;
}
}
#if HAVE_DATE_TIME_OFFSET
else
{
if ( DateTimeUtils . TryParseDateTimeOffset ( _stringReference , DateFormatString , Culture , out DateTimeOffset dt ) )
{
SetToken ( JsonToken . Date , dt , false ) ;
return ;
}
}
# endif
}
SetToken ( JsonToken . String , _stringReference . ToString ( ) , false ) ;
_quoteChar = quote ;
break ;
}
}
private static void BlockCopyChars ( char [ ] src , int srcOffset , char [ ] dst , int dstOffset , int count )
{
const int charByteCount = 2 ;
Buffer . BlockCopy ( src , srcOffset * charByteCount , dst , dstOffset * charByteCount , count * charByteCount ) ;
}
private void ShiftBufferIfNeeded ( )
{
// once in the last 10% of the buffer, or buffer is already very large then
// shift the remaining content to the start to avoid unnecessarily increasing
// the buffer size when reading numbers/strings
int length = _chars . Length ;
if ( length - _charPos < = length * 0.1 | | length > = LargeBufferLength )
{
int count = _charsUsed - _charPos ;
if ( count > 0 )
{
BlockCopyChars ( _chars , _charPos , _chars , 0 , count ) ;
}
_lineStartPos - = _charPos ;
_charPos = 0 ;
_charsUsed = count ;
_chars [ _charsUsed ] = '\0' ;
}
}
private int ReadData ( bool append )
{
return ReadData ( append , 0 ) ;
}
private void PrepareBufferForReadData ( bool append , int charsRequired )
{
// char buffer is full
if ( _charsUsed + charsRequired > = _chars . Length - 1 )
{
if ( append )
{
int doubledArrayLength = _chars . Length * 2 ;
// copy to new array either double the size of the current or big enough to fit required content
int newArrayLength = Math . Max (
doubledArrayLength < 0 ? int . MaxValue : doubledArrayLength , // handle overflow
_charsUsed + charsRequired + 1 ) ;
// increase the size of the buffer
char [ ] dst = BufferUtils . RentBuffer ( _arrayPool , newArrayLength ) ;
BlockCopyChars ( _chars , 0 , dst , 0 , _chars . Length ) ;
BufferUtils . ReturnBuffer ( _arrayPool , _chars ) ;
_chars = dst ;
}
else
{
int remainingCharCount = _charsUsed - _charPos ;
if ( remainingCharCount + charsRequired + 1 > = _chars . Length )
{
// the remaining count plus the required is bigger than the current buffer size
char [ ] dst = BufferUtils . RentBuffer ( _arrayPool , remainingCharCount + charsRequired + 1 ) ;
if ( remainingCharCount > 0 )
{
BlockCopyChars ( _chars , _charPos , dst , 0 , remainingCharCount ) ;
}
BufferUtils . ReturnBuffer ( _arrayPool , _chars ) ;
_chars = dst ;
}
else
{
// copy any remaining data to the beginning of the buffer if needed and reset positions
if ( remainingCharCount > 0 )
{
BlockCopyChars ( _chars , _charPos , _chars , 0 , remainingCharCount ) ;
}
}
_lineStartPos - = _charPos ;
_charPos = 0 ;
_charsUsed = remainingCharCount ;
}
}
}
private int ReadData ( bool append , int charsRequired )
{
if ( _isEndOfFile )
{
return 0 ;
}
PrepareBufferForReadData ( append , charsRequired ) ;
int attemptCharReadCount = _chars . Length - _charsUsed - 1 ;
int charsRead = _reader . Read ( _chars , _charsUsed , attemptCharReadCount ) ;
_charsUsed + = charsRead ;
if ( charsRead = = 0 )
{
_isEndOfFile = true ;
}
_chars [ _charsUsed ] = '\0' ;
return charsRead ;
}
private bool EnsureChars ( int relativePosition , bool append )
{
if ( _charPos + relativePosition > = _charsUsed )
{
return ReadChars ( relativePosition , append ) ;
}
return true ;
}
private bool ReadChars ( int relativePosition , bool append )
{
if ( _isEndOfFile )
{
return false ;
}
int charsRequired = _charPos + relativePosition - _charsUsed + 1 ;
int totalCharsRead = 0 ;
// it is possible that the TextReader doesn't return all data at once
// repeat read until the required text is returned or the reader is out of content
do
{
int charsRead = ReadData ( append , charsRequired - totalCharsRead ) ;
// no more content
if ( charsRead = = 0 )
{
break ;
}
totalCharsRead + = charsRead ;
} while ( totalCharsRead < charsRequired ) ;
if ( totalCharsRead < charsRequired )
{
return false ;
}
return true ;
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the next token was read successfully; <c>false</c> if there are no more tokens to read.
/// </returns>
public override bool Read ( )
{
EnsureBuffer ( ) ;
while ( true )
{
switch ( _currentState )
{
case State . Start :
case State . Property :
case State . Array :
case State . ArrayStart :
case State . Constructor :
case State . ConstructorStart :
return ParseValue ( ) ;
case State . Object :
case State . ObjectStart :
return ParseObject ( ) ;
case State . PostValue :
// returns true if it hits
// end of object or array
if ( ParsePostValue ( false ) )
{
return true ;
}
break ;
case State . Finished :
if ( EnsureChars ( 0 , false ) )
{
EatWhitespace ( ) ;
if ( _isEndOfFile )
{
SetToken ( JsonToken . None ) ;
return false ;
}
if ( _chars [ _charPos ] = = '/' )
{
ParseComment ( true ) ;
return true ;
}
throw JsonReaderException . Create ( this , "Additional text encountered after finished reading JSON content: {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
SetToken ( JsonToken . None ) ;
return false ;
default :
throw JsonReaderException . Create ( this , "Unexpected state: {0}." . FormatWith ( CultureInfo . InvariantCulture , CurrentState ) ) ;
}
}
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Int32"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="Int32"/>. This method will return <c>null</c> at the end of an array.</returns>
public override int? ReadAsInt32 ( )
{
return ( int? ) ReadNumberValue ( ReadType . ReadAsInt32 ) ;
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTime"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTime"/>. This method will return <c>null</c> at the end of an array.</returns>
public override DateTime ? ReadAsDateTime ( )
{
return ( DateTime ? ) ReadStringValue ( ReadType . ReadAsDateTime ) ;
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="String"/>.
/// </summary>
/// <returns>A <see cref="String"/>. This method will return <c>null</c> at the end of an array.</returns>
2021-03-30 10:54:25 +08:00
public override string ReadAsString ( )
2021-03-29 14:54:12 +08:00
{
2021-03-30 10:54:25 +08:00
return ( string ) ReadStringValue ( ReadType . ReadAsString ) ;
2021-03-29 14:54:12 +08:00
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Byte"/>[].
/// </summary>
/// <returns>A <see cref="Byte"/>[] or <c>null</c> if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
2021-03-30 10:54:25 +08:00
public override byte [ ] ReadAsBytes ( )
2021-03-29 14:54:12 +08:00
{
EnsureBuffer ( ) ;
bool isWrapped = false ;
switch ( _currentState )
{
case State . PostValue :
if ( ParsePostValue ( true ) )
{
return null ;
}
goto case State . Start ;
case State . Start :
case State . Property :
case State . Array :
case State . ArrayStart :
case State . Constructor :
case State . ConstructorStart :
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( ReadNullChar ( ) )
{
SetToken ( JsonToken . None , null , false ) ;
return null ;
}
break ;
case '"' :
case '\'' :
ParseString ( currentChar , ReadType . ReadAsBytes ) ;
2021-03-30 10:54:25 +08:00
byte [ ] data = ( byte [ ] ) Value ;
2021-03-29 14:54:12 +08:00
if ( isWrapped )
{
ReaderReadAndAssert ( ) ;
if ( TokenType ! = JsonToken . EndObject )
{
throw JsonReaderException . Create ( this , "Error reading bytes. Unexpected token: {0}." . FormatWith ( CultureInfo . InvariantCulture , TokenType ) ) ;
}
SetToken ( JsonToken . Bytes , data , false ) ;
}
return data ;
case '{' :
_charPos + + ;
SetToken ( JsonToken . StartObject ) ;
ReadIntoWrappedTypeObject ( ) ;
isWrapped = true ;
break ;
case '[' :
_charPos + + ;
SetToken ( JsonToken . StartArray ) ;
return ReadArrayIntoByteArray ( ) ;
case 'n' :
HandleNull ( ) ;
return null ;
case '/' :
ParseComment ( false ) ;
break ;
case ',' :
ProcessValueComma ( ) ;
break ;
case ']' :
_charPos + + ;
if ( _currentState = = State . Array | | _currentState = = State . ArrayStart | | _currentState = = State . PostValue )
{
SetToken ( JsonToken . EndArray ) ;
return null ;
}
throw CreateUnexpectedCharacterException ( currentChar ) ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
_charPos + + ;
if ( ! char . IsWhiteSpace ( currentChar ) )
{
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
// eat
break ;
}
}
case State . Finished :
ReadFinished ( ) ;
return null ;
default :
throw JsonReaderException . Create ( this , "Unexpected state: {0}." . FormatWith ( CultureInfo . InvariantCulture , CurrentState ) ) ;
}
}
2021-03-30 10:54:25 +08:00
private object ReadStringValue ( ReadType readType )
2021-03-29 14:54:12 +08:00
{
EnsureBuffer ( ) ;
switch ( _currentState )
{
case State . PostValue :
if ( ParsePostValue ( true ) )
{
return null ;
}
goto case State . Start ;
case State . Start :
case State . Property :
case State . Array :
case State . ArrayStart :
case State . Constructor :
case State . ConstructorStart :
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( ReadNullChar ( ) )
{
SetToken ( JsonToken . None , null , false ) ;
return null ;
}
break ;
case '"' :
case '\'' :
ParseString ( currentChar , readType ) ;
return FinishReadQuotedStringValue ( readType ) ;
case '-' :
if ( EnsureChars ( 1 , true ) & & _chars [ _charPos + 1 ] = = 'I' )
{
return ParseNumberNegativeInfinity ( readType ) ;
}
else
{
ParseNumber ( readType ) ;
return Value ;
}
case '.' :
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
if ( readType ! = ReadType . ReadAsString )
{
_charPos + + ;
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
ParseNumber ( ReadType . ReadAsString ) ;
return Value ;
case 't' :
case 'f' :
if ( readType ! = ReadType . ReadAsString )
{
_charPos + + ;
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
string expected = currentChar = = 't' ? JsonConvert . True : JsonConvert . False ;
if ( ! MatchValueWithTrailingSeparator ( expected ) )
{
throw CreateUnexpectedCharacterException ( _chars [ _charPos ] ) ;
}
SetToken ( JsonToken . String , expected ) ;
return expected ;
case 'I' :
return ParseNumberPositiveInfinity ( readType ) ;
case 'N' :
return ParseNumberNaN ( readType ) ;
case 'n' :
HandleNull ( ) ;
return null ;
case '/' :
ParseComment ( false ) ;
break ;
case ',' :
ProcessValueComma ( ) ;
break ;
case ']' :
_charPos + + ;
if ( _currentState = = State . Array | | _currentState = = State . ArrayStart | | _currentState = = State . PostValue )
{
SetToken ( JsonToken . EndArray ) ;
return null ;
}
throw CreateUnexpectedCharacterException ( currentChar ) ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
_charPos + + ;
if ( ! char . IsWhiteSpace ( currentChar ) )
{
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
// eat
break ;
}
}
case State . Finished :
ReadFinished ( ) ;
return null ;
default :
throw JsonReaderException . Create ( this , "Unexpected state: {0}." . FormatWith ( CultureInfo . InvariantCulture , CurrentState ) ) ;
}
}
2021-03-30 10:54:25 +08:00
private object FinishReadQuotedStringValue ( ReadType readType )
2021-03-29 14:54:12 +08:00
{
switch ( readType )
{
case ReadType . ReadAsBytes :
case ReadType . ReadAsString :
return Value ;
case ReadType . ReadAsDateTime :
if ( Value is DateTime time )
{
return time ;
}
2021-03-30 10:54:25 +08:00
return ReadDateTimeString ( ( string ) Value ) ;
2021-03-29 14:54:12 +08:00
#if HAVE_DATE_TIME_OFFSET
case ReadType . ReadAsDateTimeOffset :
if ( Value is DateTimeOffset offset )
{
return offset ;
}
2021-03-30 10:54:25 +08:00
return ReadDateTimeOffsetString ( ( string ) Value ) ;
2021-03-29 14:54:12 +08:00
# endif
default :
throw new ArgumentOutOfRangeException ( nameof ( readType ) ) ;
}
}
private JsonReaderException CreateUnexpectedCharacterException ( char c )
{
return JsonReaderException . Create ( this , "Unexpected character encountered while parsing value: {0}." . FormatWith ( CultureInfo . InvariantCulture , c ) ) ;
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Boolean"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="Boolean"/>. This method will return <c>null</c> at the end of an array.</returns>
public override bool? ReadAsBoolean ( )
{
EnsureBuffer ( ) ;
switch ( _currentState )
{
case State . PostValue :
if ( ParsePostValue ( true ) )
{
return null ;
}
goto case State . Start ;
case State . Start :
case State . Property :
case State . Array :
case State . ArrayStart :
case State . Constructor :
case State . ConstructorStart :
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( ReadNullChar ( ) )
{
SetToken ( JsonToken . None , null , false ) ;
return null ;
}
break ;
case '"' :
case '\'' :
ParseString ( currentChar , ReadType . Read ) ;
return ReadBooleanString ( _stringReference . ToString ( ) ) ;
case 'n' :
HandleNull ( ) ;
return null ;
case '-' :
case '.' :
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
ParseNumber ( ReadType . Read ) ;
bool b ;
#if HAVE_BIG_INTEGER
if ( Value is BigInteger integer )
{
b = integer ! = 0 ;
}
else
# endif
{
b = Convert . ToBoolean ( Value , CultureInfo . InvariantCulture ) ;
}
SetToken ( JsonToken . Boolean , b , false ) ;
return b ;
case 't' :
case 'f' :
bool isTrue = currentChar = = 't' ;
string expected = isTrue ? JsonConvert . True : JsonConvert . False ;
if ( ! MatchValueWithTrailingSeparator ( expected ) )
{
throw CreateUnexpectedCharacterException ( _chars [ _charPos ] ) ;
}
SetToken ( JsonToken . Boolean , isTrue ) ;
return isTrue ;
case '/' :
ParseComment ( false ) ;
break ;
case ',' :
ProcessValueComma ( ) ;
break ;
case ']' :
_charPos + + ;
if ( _currentState = = State . Array | | _currentState = = State . ArrayStart | | _currentState = = State . PostValue )
{
SetToken ( JsonToken . EndArray ) ;
return null ;
}
throw CreateUnexpectedCharacterException ( currentChar ) ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
_charPos + + ;
if ( ! char . IsWhiteSpace ( currentChar ) )
{
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
// eat
break ;
}
}
case State . Finished :
ReadFinished ( ) ;
return null ;
default :
throw JsonReaderException . Create ( this , "Unexpected state: {0}." . FormatWith ( CultureInfo . InvariantCulture , CurrentState ) ) ;
}
}
private void ProcessValueComma ( )
{
_charPos + + ;
if ( _currentState ! = State . PostValue )
{
SetToken ( JsonToken . Undefined ) ;
JsonReaderException ex = CreateUnexpectedCharacterException ( ',' ) ;
// so the comma will be parsed again
_charPos - - ;
throw ex ;
}
SetStateBasedOnCurrent ( ) ;
}
2021-03-30 10:54:25 +08:00
private object ReadNumberValue ( ReadType readType )
2021-03-29 14:54:12 +08:00
{
EnsureBuffer ( ) ;
switch ( _currentState )
{
case State . PostValue :
if ( ParsePostValue ( true ) )
{
return null ;
}
goto case State . Start ;
case State . Start :
case State . Property :
case State . Array :
case State . ArrayStart :
case State . Constructor :
case State . ConstructorStart :
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( ReadNullChar ( ) )
{
SetToken ( JsonToken . None , null , false ) ;
return null ;
}
break ;
case '"' :
case '\'' :
ParseString ( currentChar , readType ) ;
return FinishReadQuotedNumber ( readType ) ;
case 'n' :
HandleNull ( ) ;
return null ;
case 'N' :
return ParseNumberNaN ( readType ) ;
case 'I' :
return ParseNumberPositiveInfinity ( readType ) ;
case '-' :
if ( EnsureChars ( 1 , true ) & & _chars [ _charPos + 1 ] = = 'I' )
{
return ParseNumberNegativeInfinity ( readType ) ;
}
else
{
ParseNumber ( readType ) ;
return Value ;
}
case '.' :
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
ParseNumber ( readType ) ;
return Value ;
case '/' :
ParseComment ( false ) ;
break ;
case ',' :
ProcessValueComma ( ) ;
break ;
case ']' :
_charPos + + ;
if ( _currentState = = State . Array | | _currentState = = State . ArrayStart | | _currentState = = State . PostValue )
{
SetToken ( JsonToken . EndArray ) ;
return null ;
}
throw CreateUnexpectedCharacterException ( currentChar ) ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
_charPos + + ;
if ( ! char . IsWhiteSpace ( currentChar ) )
{
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
// eat
break ;
}
}
case State . Finished :
ReadFinished ( ) ;
return null ;
default :
throw JsonReaderException . Create ( this , "Unexpected state: {0}." . FormatWith ( CultureInfo . InvariantCulture , CurrentState ) ) ;
}
}
2021-03-30 10:54:25 +08:00
private object FinishReadQuotedNumber ( ReadType readType )
2021-03-29 14:54:12 +08:00
{
switch ( readType )
{
case ReadType . ReadAsInt32 :
return ReadInt32String ( _stringReference . ToString ( ) ) ;
case ReadType . ReadAsDecimal :
return ReadDecimalString ( _stringReference . ToString ( ) ) ;
case ReadType . ReadAsDouble :
return ReadDoubleString ( _stringReference . ToString ( ) ) ;
default :
throw new ArgumentOutOfRangeException ( nameof ( readType ) ) ;
}
}
#if HAVE_DATE_TIME_OFFSET
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/>. This method will return <c>null</c> at the end of an array.</returns>
public override DateTimeOffset ? ReadAsDateTimeOffset ( )
{
return ( DateTimeOffset ? ) ReadStringValue ( ReadType . ReadAsDateTimeOffset ) ;
}
# endif
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Decimal"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="Decimal"/>. This method will return <c>null</c> at the end of an array.</returns>
public override decimal? ReadAsDecimal ( )
{
return ( decimal? ) ReadNumberValue ( ReadType . ReadAsDecimal ) ;
}
/// <summary>
/// Reads the next JSON token from the underlying <see cref="TextReader"/> as a <see cref="Nullable{T}"/> of <see cref="Double"/>.
/// </summary>
/// <returns>A <see cref="Nullable{T}"/> of <see cref="Double"/>. This method will return <c>null</c> at the end of an array.</returns>
public override double? ReadAsDouble ( )
{
return ( double? ) ReadNumberValue ( ReadType . ReadAsDouble ) ;
}
private void HandleNull ( )
{
if ( EnsureChars ( 1 , true ) )
{
char next = _chars [ _charPos + 1 ] ;
if ( next = = 'u' )
{
ParseNull ( ) ;
return ;
}
_charPos + = 2 ;
throw CreateUnexpectedCharacterException ( _chars [ _charPos - 1 ] ) ;
}
_charPos = _charsUsed ;
throw CreateUnexpectedEndException ( ) ;
}
private void ReadFinished ( )
{
if ( EnsureChars ( 0 , false ) )
{
EatWhitespace ( ) ;
if ( _isEndOfFile )
{
return ;
}
if ( _chars [ _charPos ] = = '/' )
{
ParseComment ( false ) ;
}
else
{
throw JsonReaderException . Create ( this , "Additional text encountered after finished reading JSON content: {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
}
SetToken ( JsonToken . None ) ;
}
private bool ReadNullChar ( )
{
if ( _charsUsed = = _charPos )
{
if ( ReadData ( false ) = = 0 )
{
_isEndOfFile = true ;
return true ;
}
}
else
{
_charPos + + ;
}
return false ;
}
private void EnsureBuffer ( )
{
if ( _chars = = null )
{
_chars = BufferUtils . RentBuffer ( _arrayPool , 1024 ) ;
_chars [ 0 ] = '\0' ;
}
}
private void ReadStringIntoBuffer ( char quote )
{
int charPos = _charPos ;
int initialPosition = _charPos ;
int lastWritePosition = _charPos ;
_stringBuffer . Position = 0 ;
while ( true )
{
switch ( _chars [ charPos + + ] )
{
case '\0' :
if ( _charsUsed = = charPos - 1 )
{
charPos - - ;
if ( ReadData ( true ) = = 0 )
{
_charPos = charPos ;
throw JsonReaderException . Create ( this , "Unterminated string. Expected delimiter: {0}." . FormatWith ( CultureInfo . InvariantCulture , quote ) ) ;
}
}
break ;
case '\\' :
_charPos = charPos ;
if ( ! EnsureChars ( 0 , true ) )
{
throw JsonReaderException . Create ( this , "Unterminated string. Expected delimiter: {0}." . FormatWith ( CultureInfo . InvariantCulture , quote ) ) ;
}
// start of escape sequence
int escapeStartPos = charPos - 1 ;
char currentChar = _chars [ charPos ] ;
charPos + + ;
char writeChar ;
switch ( currentChar )
{
case 'b' :
writeChar = '\b' ;
break ;
case 't' :
writeChar = '\t' ;
break ;
case 'n' :
writeChar = '\n' ;
break ;
case 'f' :
writeChar = '\f' ;
break ;
case 'r' :
writeChar = '\r' ;
break ;
case '\\' :
writeChar = '\\' ;
break ;
case '"' :
case '\'' :
case '/' :
writeChar = currentChar ;
break ;
case 'u' :
_charPos = charPos ;
writeChar = ParseUnicode ( ) ;
if ( StringUtils . IsLowSurrogate ( writeChar ) )
{
// low surrogate with no preceding high surrogate; this char is replaced
writeChar = UnicodeReplacementChar ;
}
else if ( StringUtils . IsHighSurrogate ( writeChar ) )
{
bool anotherHighSurrogate ;
// loop for handling situations where there are multiple consecutive high surrogates
do
{
anotherHighSurrogate = false ;
// potential start of a surrogate pair
if ( EnsureChars ( 2 , true ) & & _chars [ _charPos ] = = '\\' & & _chars [ _charPos + 1 ] = = 'u' )
{
char highSurrogate = writeChar ;
_charPos + = 2 ;
writeChar = ParseUnicode ( ) ;
if ( StringUtils . IsLowSurrogate ( writeChar ) )
{
// a valid surrogate pair!
}
else if ( StringUtils . IsHighSurrogate ( writeChar ) )
{
// another high surrogate; replace current and start check over
highSurrogate = UnicodeReplacementChar ;
anotherHighSurrogate = true ;
}
else
{
// high surrogate not followed by low surrogate; original char is replaced
highSurrogate = UnicodeReplacementChar ;
}
EnsureBufferNotEmpty ( ) ;
WriteCharToBuffer ( highSurrogate , lastWritePosition , escapeStartPos ) ;
lastWritePosition = _charPos ;
}
else
{
// there are not enough remaining chars for the low surrogate or is not follow by unicode sequence
// replace high surrogate and continue on as usual
writeChar = UnicodeReplacementChar ;
}
} while ( anotherHighSurrogate ) ;
}
charPos = _charPos ;
break ;
default :
_charPos = charPos ;
throw JsonReaderException . Create ( this , "Bad JSON escape sequence: {0}." . FormatWith ( CultureInfo . InvariantCulture , @"\" + currentChar ) ) ;
}
EnsureBufferNotEmpty ( ) ;
WriteCharToBuffer ( writeChar , lastWritePosition , escapeStartPos ) ;
lastWritePosition = charPos ;
break ;
case StringUtils . CarriageReturn :
_charPos = charPos - 1 ;
ProcessCarriageReturn ( true ) ;
charPos = _charPos ;
break ;
case StringUtils . LineFeed :
_charPos = charPos - 1 ;
ProcessLineFeed ( ) ;
charPos = _charPos ;
break ;
case '"' :
case '\'' :
if ( _chars [ charPos - 1 ] = = quote )
{
FinishReadStringIntoBuffer ( charPos - 1 , initialPosition , lastWritePosition ) ;
return ;
}
break ;
}
}
}
private void FinishReadStringIntoBuffer ( int charPos , int initialPosition , int lastWritePosition )
{
if ( initialPosition = = lastWritePosition )
{
_stringReference = new StringReference ( _chars , initialPosition , charPos - initialPosition ) ;
}
else
{
EnsureBufferNotEmpty ( ) ;
if ( charPos > lastWritePosition )
{
_stringBuffer . Append ( _arrayPool , _chars , lastWritePosition , charPos - lastWritePosition ) ;
}
2021-03-30 10:54:25 +08:00
_stringReference = new StringReference ( _stringBuffer . InternalBuffer , 0 , _stringBuffer . Position ) ;
2021-03-29 14:54:12 +08:00
}
_charPos = charPos + 1 ;
}
private void WriteCharToBuffer ( char writeChar , int lastWritePosition , int writeToPosition )
{
if ( writeToPosition > lastWritePosition )
{
_stringBuffer . Append ( _arrayPool , _chars , lastWritePosition , writeToPosition - lastWritePosition ) ;
}
_stringBuffer . Append ( _arrayPool , writeChar ) ;
}
private char ConvertUnicode ( bool enoughChars )
{
if ( enoughChars )
{
if ( ConvertUtils . TryHexTextToInt ( _chars , _charPos , _charPos + 4 , out int value ) )
{
char hexChar = Convert . ToChar ( value ) ;
_charPos + = 4 ;
return hexChar ;
}
else
{
throw JsonReaderException . Create ( this , @"Invalid Unicode escape sequence: \u{0}." . FormatWith ( CultureInfo . InvariantCulture , new string ( _chars , _charPos , 4 ) ) ) ;
}
}
else
{
throw JsonReaderException . Create ( this , "Unexpected end while parsing Unicode escape sequence." ) ;
}
}
private char ParseUnicode ( )
{
return ConvertUnicode ( EnsureChars ( 4 , true ) ) ;
}
private void ReadNumberIntoBuffer ( )
{
int charPos = _charPos ;
while ( true )
{
char currentChar = _chars [ charPos ] ;
if ( currentChar = = '\0' )
{
_charPos = charPos ;
if ( _charsUsed = = charPos )
{
if ( ReadData ( true ) = = 0 )
{
return ;
}
}
else
{
return ;
}
}
else if ( ReadNumberCharIntoBuffer ( currentChar , charPos ) )
{
return ;
}
else
{
charPos + + ;
}
}
}
private bool ReadNumberCharIntoBuffer ( char currentChar , int charPos )
{
switch ( currentChar )
{
case '-' :
case '+' :
case 'a' :
case 'A' :
case 'b' :
case 'B' :
case 'c' :
case 'C' :
case 'd' :
case 'D' :
case 'e' :
case 'E' :
case 'f' :
case 'F' :
case 'x' :
case 'X' :
case '.' :
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
return false ;
default :
_charPos = charPos ;
if ( char . IsWhiteSpace ( currentChar ) | | currentChar = = ',' | | currentChar = = '}' | | currentChar = = ']' | | currentChar = = ')' | | currentChar = = '/' )
{
return true ;
}
throw JsonReaderException . Create ( this , "Unexpected character encountered while parsing number: {0}." . FormatWith ( CultureInfo . InvariantCulture , currentChar ) ) ;
}
}
private void ClearRecentString ( )
{
_stringBuffer . Position = 0 ;
_stringReference = new StringReference ( ) ;
}
private bool ParsePostValue ( bool ignoreComments )
{
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( _charsUsed = = _charPos )
{
if ( ReadData ( false ) = = 0 )
{
_currentState = State . Finished ;
return false ;
}
}
else
{
_charPos + + ;
}
break ;
case '}' :
_charPos + + ;
SetToken ( JsonToken . EndObject ) ;
return true ;
case ']' :
_charPos + + ;
SetToken ( JsonToken . EndArray ) ;
return true ;
case ')' :
_charPos + + ;
SetToken ( JsonToken . EndConstructor ) ;
return true ;
case '/' :
ParseComment ( ! ignoreComments ) ;
if ( ! ignoreComments )
{
return true ;
}
break ;
case ',' :
_charPos + + ;
// finished parsing
SetStateBasedOnCurrent ( ) ;
return false ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
default :
if ( char . IsWhiteSpace ( currentChar ) )
{
// eat
_charPos + + ;
}
else
{
// handle multiple content without comma delimiter
if ( SupportMultipleContent & & Depth = = 0 )
{
SetStateBasedOnCurrent ( ) ;
return false ;
}
throw JsonReaderException . Create ( this , "After parsing a value an unexpected character was encountered: {0}." . FormatWith ( CultureInfo . InvariantCulture , currentChar ) ) ;
}
break ;
}
}
}
private bool ParseObject ( )
{
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( _charsUsed = = _charPos )
{
if ( ReadData ( false ) = = 0 )
{
return false ;
}
}
else
{
_charPos + + ;
}
break ;
case '}' :
SetToken ( JsonToken . EndObject ) ;
_charPos + + ;
return true ;
case '/' :
ParseComment ( true ) ;
return true ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
if ( char . IsWhiteSpace ( currentChar ) )
{
// eat
_charPos + + ;
}
else
{
return ParseProperty ( ) ;
}
break ;
}
}
}
private bool ParseProperty ( )
{
char firstChar = _chars [ _charPos ] ;
char quoteChar ;
if ( firstChar = = '"' | | firstChar = = '\'' )
{
_charPos + + ;
quoteChar = firstChar ;
ShiftBufferIfNeeded ( ) ;
ReadStringIntoBuffer ( quoteChar ) ;
}
else if ( ValidIdentifierChar ( firstChar ) )
{
quoteChar = '\0' ;
ShiftBufferIfNeeded ( ) ;
ParseUnquotedProperty ( ) ;
}
else
{
throw JsonReaderException . Create ( this , "Invalid property identifier character: {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
2021-03-30 10:54:25 +08:00
string propertyName ;
2021-03-29 14:54:12 +08:00
if ( PropertyNameTable ! = null )
{
propertyName = PropertyNameTable . Get ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length ) ;
// no match in name table
if ( propertyName = = null )
{
propertyName = _stringReference . ToString ( ) ;
}
}
else
{
propertyName = _stringReference . ToString ( ) ;
}
EatWhitespace ( ) ;
if ( _chars [ _charPos ] ! = ':' )
{
throw JsonReaderException . Create ( this , "Invalid character after parsing property name. Expected ':' but got: {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
_charPos + + ;
SetToken ( JsonToken . PropertyName , propertyName ) ;
_quoteChar = quoteChar ;
ClearRecentString ( ) ;
return true ;
}
private bool ValidIdentifierChar ( char value )
{
return ( char . IsLetterOrDigit ( value ) | | value = = '_' | | value = = '$' ) ;
}
private void ParseUnquotedProperty ( )
{
int initialPosition = _charPos ;
// parse unquoted property name until whitespace or colon
while ( true )
{
char currentChar = _chars [ _charPos ] ;
if ( currentChar = = '\0' )
{
if ( _charsUsed = = _charPos )
{
if ( ReadData ( true ) = = 0 )
{
throw JsonReaderException . Create ( this , "Unexpected end while parsing unquoted property name." ) ;
}
continue ;
}
_stringReference = new StringReference ( _chars , initialPosition , _charPos - initialPosition ) ;
return ;
}
if ( ReadUnquotedPropertyReportIfDone ( currentChar , initialPosition ) )
{
return ;
}
}
}
private bool ReadUnquotedPropertyReportIfDone ( char currentChar , int initialPosition )
{
if ( ValidIdentifierChar ( currentChar ) )
{
_charPos + + ;
return false ;
}
if ( char . IsWhiteSpace ( currentChar ) | | currentChar = = ':' )
{
_stringReference = new StringReference ( _chars , initialPosition , _charPos - initialPosition ) ;
return true ;
}
throw JsonReaderException . Create ( this , "Invalid JavaScript property identifier character: {0}." . FormatWith ( CultureInfo . InvariantCulture , currentChar ) ) ;
}
private bool ParseValue ( )
{
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( _charsUsed = = _charPos )
{
if ( ReadData ( false ) = = 0 )
{
return false ;
}
}
else
{
_charPos + + ;
}
break ;
case '"' :
case '\'' :
ParseString ( currentChar , ReadType . Read ) ;
return true ;
case 't' :
ParseTrue ( ) ;
return true ;
case 'f' :
ParseFalse ( ) ;
return true ;
case 'n' :
if ( EnsureChars ( 1 , true ) )
{
char next = _chars [ _charPos + 1 ] ;
if ( next = = 'u' )
{
ParseNull ( ) ;
}
else if ( next = = 'e' )
{
ParseConstructor ( ) ;
}
else
{
throw CreateUnexpectedCharacterException ( _chars [ _charPos ] ) ;
}
}
else
{
_charPos + + ;
throw CreateUnexpectedEndException ( ) ;
}
return true ;
case 'N' :
ParseNumberNaN ( ReadType . Read ) ;
return true ;
case 'I' :
ParseNumberPositiveInfinity ( ReadType . Read ) ;
return true ;
case '-' :
if ( EnsureChars ( 1 , true ) & & _chars [ _charPos + 1 ] = = 'I' )
{
ParseNumberNegativeInfinity ( ReadType . Read ) ;
}
else
{
ParseNumber ( ReadType . Read ) ;
}
return true ;
case '/' :
ParseComment ( true ) ;
return true ;
case 'u' :
ParseUndefined ( ) ;
return true ;
case '{' :
_charPos + + ;
SetToken ( JsonToken . StartObject ) ;
return true ;
case '[' :
_charPos + + ;
SetToken ( JsonToken . StartArray ) ;
return true ;
case ']' :
_charPos + + ;
SetToken ( JsonToken . EndArray ) ;
return true ;
case ',' :
// don't increment position, the next call to read will handle comma
// this is done to handle multiple empty comma values
SetToken ( JsonToken . Undefined ) ;
return true ;
case ')' :
_charPos + + ;
SetToken ( JsonToken . EndConstructor ) ;
return true ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
case ' ' :
case StringUtils . Tab :
// eat
_charPos + + ;
break ;
default :
if ( char . IsWhiteSpace ( currentChar ) )
{
// eat
_charPos + + ;
break ;
}
if ( char . IsNumber ( currentChar ) | | currentChar = = '-' | | currentChar = = '.' )
{
ParseNumber ( ReadType . Read ) ;
return true ;
}
throw CreateUnexpectedCharacterException ( currentChar ) ;
}
}
}
private void ProcessLineFeed ( )
{
_charPos + + ;
OnNewLine ( _charPos ) ;
}
private void ProcessCarriageReturn ( bool append )
{
_charPos + + ;
SetNewLine ( EnsureChars ( 1 , append ) ) ;
}
private void EatWhitespace ( )
{
while ( true )
{
char currentChar = _chars [ _charPos ] ;
switch ( currentChar )
{
case '\0' :
if ( _charsUsed = = _charPos )
{
if ( ReadData ( false ) = = 0 )
{
return ;
}
}
else
{
_charPos + + ;
}
break ;
case StringUtils . CarriageReturn :
ProcessCarriageReturn ( false ) ;
break ;
case StringUtils . LineFeed :
ProcessLineFeed ( ) ;
break ;
default :
if ( currentChar = = ' ' | | char . IsWhiteSpace ( currentChar ) )
{
_charPos + + ;
}
else
{
return ;
}
break ;
}
}
}
private void ParseConstructor ( )
{
if ( MatchValueWithTrailingSeparator ( "new" ) )
{
EatWhitespace ( ) ;
int initialPosition = _charPos ;
int endPosition ;
while ( true )
{
char currentChar = _chars [ _charPos ] ;
if ( currentChar = = '\0' )
{
if ( _charsUsed = = _charPos )
{
if ( ReadData ( true ) = = 0 )
{
throw JsonReaderException . Create ( this , "Unexpected end while parsing constructor." ) ;
}
}
else
{
endPosition = _charPos ;
_charPos + + ;
break ;
}
}
else if ( char . IsLetterOrDigit ( currentChar ) )
{
_charPos + + ;
}
else if ( currentChar = = StringUtils . CarriageReturn )
{
endPosition = _charPos ;
ProcessCarriageReturn ( true ) ;
break ;
}
else if ( currentChar = = StringUtils . LineFeed )
{
endPosition = _charPos ;
ProcessLineFeed ( ) ;
break ;
}
else if ( char . IsWhiteSpace ( currentChar ) )
{
endPosition = _charPos ;
_charPos + + ;
break ;
}
else if ( currentChar = = '(' )
{
endPosition = _charPos ;
break ;
}
else
{
throw JsonReaderException . Create ( this , "Unexpected character while parsing constructor: {0}." . FormatWith ( CultureInfo . InvariantCulture , currentChar ) ) ;
}
}
_stringReference = new StringReference ( _chars , initialPosition , endPosition - initialPosition ) ;
string constructorName = _stringReference . ToString ( ) ;
EatWhitespace ( ) ;
if ( _chars [ _charPos ] ! = '(' )
{
throw JsonReaderException . Create ( this , "Unexpected character while parsing constructor: {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
_charPos + + ;
ClearRecentString ( ) ;
SetToken ( JsonToken . StartConstructor , constructorName ) ;
}
else
{
throw JsonReaderException . Create ( this , "Unexpected content while parsing JSON." ) ;
}
}
private void ParseNumber ( ReadType readType )
{
ShiftBufferIfNeeded ( ) ;
char firstChar = _chars [ _charPos ] ;
int initialPosition = _charPos ;
ReadNumberIntoBuffer ( ) ;
ParseReadNumber ( readType , firstChar , initialPosition ) ;
}
private void ParseReadNumber ( ReadType readType , char firstChar , int initialPosition )
2021-03-30 10:54:25 +08:00
{
2021-03-29 14:54:12 +08:00
// set state to PostValue now so that if there is an error parsing the number then the reader can continue
SetPostValueState ( true ) ;
_stringReference = new StringReference ( _chars , initialPosition , _charPos - initialPosition ) ;
object numberValue ;
JsonToken numberType ;
bool singleDigit = ( char . IsDigit ( firstChar ) & & _stringReference . Length = = 1 ) ;
bool nonBase10 = ( firstChar = = '0' & & _stringReference . Length > 1 & & _stringReference . Chars [ _stringReference . StartIndex + 1 ] ! = '.' & & _stringReference . Chars [ _stringReference . StartIndex + 1 ] ! = 'e' & & _stringReference . Chars [ _stringReference . StartIndex + 1 ] ! = 'E' ) ;
switch ( readType )
{
case ReadType . ReadAsString :
{
string number = _stringReference . ToString ( ) ;
// validate that the string is a valid number
if ( nonBase10 )
{
try
{
if ( number . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) )
{
Convert . ToInt64 ( number , 16 ) ;
}
else
{
Convert . ToInt64 ( number , 8 ) ;
}
}
catch ( Exception ex )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid number." . FormatWith ( CultureInfo . InvariantCulture , number ) , ex ) ;
}
}
else
{
if ( ! double . TryParse ( number , NumberStyles . Float , CultureInfo . InvariantCulture , out _ ) )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid number." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
numberType = JsonToken . String ;
numberValue = number ;
}
break ;
case ReadType . ReadAsInt32 :
{
if ( singleDigit )
{
// digit char values start at 48
numberValue = firstChar - 48 ;
}
else if ( nonBase10 )
{
string number = _stringReference . ToString ( ) ;
try
{
int integer = number . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) ? Convert . ToInt32 ( number , 16 ) : Convert . ToInt32 ( number , 8 ) ;
numberValue = integer ;
}
catch ( Exception ex )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid integer." . FormatWith ( CultureInfo . InvariantCulture , number ) , ex ) ;
}
}
else
{
ParseResult parseResult = ConvertUtils . Int32TryParse ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length , out int value ) ;
if ( parseResult = = ParseResult . Success )
{
numberValue = value ;
}
else if ( parseResult = = ParseResult . Overflow )
{
throw ThrowReaderError ( "JSON integer {0} is too large or small for an Int32." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
else
{
throw ThrowReaderError ( "Input string '{0}' is not a valid integer." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
numberType = JsonToken . Integer ;
}
break ;
case ReadType . ReadAsDecimal :
{
if ( singleDigit )
{
// digit char values start at 48
numberValue = ( decimal ) firstChar - 48 ;
}
else if ( nonBase10 )
{
string number = _stringReference . ToString ( ) ;
try
{
// decimal.Parse doesn't support parsing hexadecimal values
long integer = number . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) ? Convert . ToInt64 ( number , 16 ) : Convert . ToInt64 ( number , 8 ) ;
numberValue = Convert . ToDecimal ( integer ) ;
}
catch ( Exception ex )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid decimal." . FormatWith ( CultureInfo . InvariantCulture , number ) , ex ) ;
}
}
else
{
ParseResult parseResult = ConvertUtils . DecimalTryParse ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length , out decimal value ) ;
if ( parseResult = = ParseResult . Success )
{
numberValue = value ;
}
else
{
throw ThrowReaderError ( "Input string '{0}' is not a valid decimal." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
numberType = JsonToken . Float ;
}
break ;
case ReadType . ReadAsDouble :
{
if ( singleDigit )
{
// digit char values start at 48
numberValue = ( double ) firstChar - 48 ;
}
else if ( nonBase10 )
{
string number = _stringReference . ToString ( ) ;
try
{
// double.Parse doesn't support parsing hexadecimal values
long integer = number . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) ? Convert . ToInt64 ( number , 16 ) : Convert . ToInt64 ( number , 8 ) ;
numberValue = Convert . ToDouble ( integer ) ;
}
catch ( Exception ex )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid double." . FormatWith ( CultureInfo . InvariantCulture , number ) , ex ) ;
}
}
else
{
string number = _stringReference . ToString ( ) ;
if ( double . TryParse ( number , NumberStyles . Float , CultureInfo . InvariantCulture , out double value ) )
{
numberValue = value ;
}
else
{
throw ThrowReaderError ( "Input string '{0}' is not a valid double." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
numberType = JsonToken . Float ;
}
break ;
case ReadType . Read :
case ReadType . ReadAsInt64 :
{
if ( singleDigit )
{
// digit char values start at 48
numberValue = ( long ) firstChar - 48 ;
numberType = JsonToken . Integer ;
}
else if ( nonBase10 )
{
string number = _stringReference . ToString ( ) ;
try
{
numberValue = number . StartsWith ( "0x" , StringComparison . OrdinalIgnoreCase ) ? Convert . ToInt64 ( number , 16 ) : Convert . ToInt64 ( number , 8 ) ;
}
catch ( Exception ex )
{
throw ThrowReaderError ( "Input string '{0}' is not a valid number." . FormatWith ( CultureInfo . InvariantCulture , number ) , ex ) ;
}
numberType = JsonToken . Integer ;
}
else
{
ParseResult parseResult = ConvertUtils . Int64TryParse ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length , out long value ) ;
if ( parseResult = = ParseResult . Success )
{
numberValue = value ;
numberType = JsonToken . Integer ;
}
else if ( parseResult = = ParseResult . Overflow )
{
#if HAVE_BIG_INTEGER
string number = _stringReference . ToString ( ) ;
if ( number . Length > MaximumJavascriptIntegerCharacterLength )
{
throw ThrowReaderError ( "JSON integer {0} is too large to parse." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
numberValue = BigIntegerParse ( number , CultureInfo . InvariantCulture ) ;
numberType = JsonToken . Integer ;
# else
throw ThrowReaderError ( "JSON integer {0} is too large or small for an Int64." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
# endif
}
else
{
if ( _floatParseHandling = = FloatParseHandling . Decimal )
{
parseResult = ConvertUtils . DecimalTryParse ( _stringReference . Chars , _stringReference . StartIndex , _stringReference . Length , out decimal d ) ;
if ( parseResult = = ParseResult . Success )
{
numberValue = d ;
}
else
{
throw ThrowReaderError ( "Input string '{0}' is not a valid decimal." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
else
{
string number = _stringReference . ToString ( ) ;
if ( double . TryParse ( number , NumberStyles . Float , CultureInfo . InvariantCulture , out double d ) )
{
numberValue = d ;
}
else
{
throw ThrowReaderError ( "Input string '{0}' is not a valid number." . FormatWith ( CultureInfo . InvariantCulture , _stringReference . ToString ( ) ) ) ;
}
}
numberType = JsonToken . Float ;
}
}
}
break ;
default :
throw JsonReaderException . Create ( this , "Cannot read number value as type." ) ;
}
ClearRecentString ( ) ;
// index has already been updated
SetToken ( numberType , numberValue , false ) ;
}
2021-03-30 10:54:25 +08:00
private JsonReaderException ThrowReaderError ( string message , Exception ex = null )
2021-03-29 14:54:12 +08:00
{
SetToken ( JsonToken . Undefined , null , false ) ;
return JsonReaderException . Create ( this , message , ex ) ;
}
#if HAVE_BIG_INTEGER
// By using the BigInteger type in a separate method,
// the runtime can execute the ParseNumber even if
// the System.Numerics.BigInteger.Parse method is
// missing, which happens in some versions of Mono
[MethodImpl(MethodImplOptions.NoInlining)]
private static object BigIntegerParse ( string number , CultureInfo culture )
{
return System . Numerics . BigInteger . Parse ( number , culture ) ;
}
# endif
private void ParseComment ( bool setToken )
{
// should have already parsed / character before reaching this method
_charPos + + ;
if ( ! EnsureChars ( 1 , false ) )
{
throw JsonReaderException . Create ( this , "Unexpected end while parsing comment." ) ;
}
bool singlelineComment ;
if ( _chars [ _charPos ] = = '*' )
{
singlelineComment = false ;
}
else if ( _chars [ _charPos ] = = '/' )
{
singlelineComment = true ;
}
else
{
throw JsonReaderException . Create ( this , "Error parsing comment. Expected: *, got {0}." . FormatWith ( CultureInfo . InvariantCulture , _chars [ _charPos ] ) ) ;
}
_charPos + + ;
int initialPosition = _charPos ;
while ( true )
{
switch ( _chars [ _charPos ] )
{
case '\0' :
if ( _charsUsed = = _charPos )
{
if ( ReadData ( true ) = = 0 )
{
if ( ! singlelineComment )
{
throw JsonReaderException . Create ( this , "Unexpected end while parsing comment." ) ;
}
EndComment ( setToken , initialPosition , _charPos ) ;
return ;
}
}
else
{
_charPos + + ;
}
break ;
case '*' :
_charPos + + ;
if ( ! singlelineComment )
{
if ( EnsureChars ( 0 , true ) )
{
if ( _chars [ _charPos ] = = '/' )
{
EndComment ( setToken , initialPosition , _charPos - 1 ) ;
_charPos + + ;
return ;
}
}
}
break ;
case StringUtils . CarriageReturn :
if ( singlelineComment )
{
EndComment ( setToken , initialPosition , _charPos ) ;
return ;
}
ProcessCarriageReturn ( true ) ;
break ;
case StringUtils . LineFeed :
if ( singlelineComment )
{
EndComment ( setToken , initialPosition , _charPos ) ;
return ;
}
ProcessLineFeed ( ) ;
break ;
default :
_charPos + + ;
break ;
}
}
}
private void EndComment ( bool setToken , int initialPosition , int endPosition )
{
if ( setToken )
{
SetToken ( JsonToken . Comment , new string ( _chars , initialPosition , endPosition - initialPosition ) ) ;
}
}
private bool MatchValue ( string value )
{
return MatchValue ( EnsureChars ( value . Length - 1 , true ) , value ) ;
}
private bool MatchValue ( bool enoughChars , string value )
{
if ( ! enoughChars )
{
_charPos = _charsUsed ;
throw CreateUnexpectedEndException ( ) ;
}
for ( int i = 0 ; i < value . Length ; i + + )
{
if ( _chars [ _charPos + i ] ! = value [ i ] )
{
_charPos + = i ;
return false ;
}
}
_charPos + = value . Length ;
return true ;
}
private bool MatchValueWithTrailingSeparator ( string value )
{
// will match value and then move to the next character, checking that it is a separator character
bool match = MatchValue ( value ) ;
if ( ! match )
{
return false ;
}
if ( ! EnsureChars ( 0 , false ) )
{
return true ;
}
return IsSeparator ( _chars [ _charPos ] ) | | _chars [ _charPos ] = = '\0' ;
}
private bool IsSeparator ( char c )
{
switch ( c )
{
case '}' :
case ']' :
case ',' :
return true ;
case '/' :
// check next character to see if start of a comment
if ( ! EnsureChars ( 1 , false ) )
{
return false ;
}
char nextChart = _chars [ _charPos + 1 ] ;
return ( nextChart = = '*' | | nextChart = = '/' ) ;
case ')' :
if ( CurrentState = = State . Constructor | | CurrentState = = State . ConstructorStart )
{
return true ;
}
break ;
case ' ' :
case StringUtils . Tab :
case StringUtils . LineFeed :
case StringUtils . CarriageReturn :
return true ;
default :
if ( char . IsWhiteSpace ( c ) )
{
return true ;
}
break ;
}
return false ;
}
private void ParseTrue ( )
{
// check characters equal 'true'
// and that it is followed by either a separator character
// or the text ends
if ( MatchValueWithTrailingSeparator ( JsonConvert . True ) )
{
SetToken ( JsonToken . Boolean , true ) ;
}
else
{
throw JsonReaderException . Create ( this , "Error parsing boolean value." ) ;
}
}
private void ParseNull ( )
{
if ( MatchValueWithTrailingSeparator ( JsonConvert . Null ) )
{
SetToken ( JsonToken . Null ) ;
}
else
{
throw JsonReaderException . Create ( this , "Error parsing null value." ) ;
}
}
private void ParseUndefined ( )
{
if ( MatchValueWithTrailingSeparator ( JsonConvert . Undefined ) )
{
SetToken ( JsonToken . Undefined ) ;
}
else
{
throw JsonReaderException . Create ( this , "Error parsing undefined value." ) ;
}
}
private void ParseFalse ( )
{
if ( MatchValueWithTrailingSeparator ( JsonConvert . False ) )
{
SetToken ( JsonToken . Boolean , false ) ;
}
else
{
throw JsonReaderException . Create ( this , "Error parsing boolean value." ) ;
}
}
private object ParseNumberNegativeInfinity ( ReadType readType )
{
return ParseNumberNegativeInfinity ( readType , MatchValueWithTrailingSeparator ( JsonConvert . NegativeInfinity ) ) ;
}
private object ParseNumberNegativeInfinity ( ReadType readType , bool matched )
{
if ( matched )
{
switch ( readType )
{
case ReadType . Read :
case ReadType . ReadAsDouble :
if ( _floatParseHandling = = FloatParseHandling . Double )
{
SetToken ( JsonToken . Float , double . NegativeInfinity ) ;
return double . NegativeInfinity ;
}
break ;
case ReadType . ReadAsString :
SetToken ( JsonToken . String , JsonConvert . NegativeInfinity ) ;
return JsonConvert . NegativeInfinity ;
}
throw JsonReaderException . Create ( this , "Cannot read -Infinity value." ) ;
}
throw JsonReaderException . Create ( this , "Error parsing -Infinity value." ) ;
}
private object ParseNumberPositiveInfinity ( ReadType readType )
{
return ParseNumberPositiveInfinity ( readType , MatchValueWithTrailingSeparator ( JsonConvert . PositiveInfinity ) ) ;
}
private object ParseNumberPositiveInfinity ( ReadType readType , bool matched )
{
if ( matched )
{
switch ( readType )
{
case ReadType . Read :
case ReadType . ReadAsDouble :
if ( _floatParseHandling = = FloatParseHandling . Double )
{
SetToken ( JsonToken . Float , double . PositiveInfinity ) ;
return double . PositiveInfinity ;
}
break ;
case ReadType . ReadAsString :
SetToken ( JsonToken . String , JsonConvert . PositiveInfinity ) ;
return JsonConvert . PositiveInfinity ;
}
throw JsonReaderException . Create ( this , "Cannot read Infinity value." ) ;
}
throw JsonReaderException . Create ( this , "Error parsing Infinity value." ) ;
}
private object ParseNumberNaN ( ReadType readType )
{
return ParseNumberNaN ( readType , MatchValueWithTrailingSeparator ( JsonConvert . NaN ) ) ;
}
private object ParseNumberNaN ( ReadType readType , bool matched )
{
if ( matched )
{
switch ( readType )
{
case ReadType . Read :
case ReadType . ReadAsDouble :
if ( _floatParseHandling = = FloatParseHandling . Double )
{
SetToken ( JsonToken . Float , double . NaN ) ;
return double . NaN ;
}
break ;
case ReadType . ReadAsString :
SetToken ( JsonToken . String , JsonConvert . NaN ) ;
return JsonConvert . NaN ;
}
throw JsonReaderException . Create ( this , "Cannot read NaN value." ) ;
}
throw JsonReaderException . Create ( this , "Error parsing NaN value." ) ;
}
/// <summary>
/// Changes the reader's state to <see cref="JsonReader.State.Closed"/>.
/// If <see cref="JsonReader.CloseInput"/> is set to <c>true</c>, the underlying <see cref="TextReader"/> is also closed.
/// </summary>
public override void Close ( )
{
base . Close ( ) ;
if ( _chars ! = null )
{
BufferUtils . ReturnBuffer ( _arrayPool , _chars ) ;
_chars = null ;
}
if ( CloseInput )
{
#if HAVE_STREAM_READER_WRITER_CLOSE
_reader ? . Close ( ) ;
# else
_reader ? . Dispose ( ) ;
# endif
}
_stringBuffer . Clear ( _arrayPool ) ;
}
/// <summary>
/// Gets a value indicating whether the class can return line information.
/// </summary>
/// <returns>
/// <c>true</c> if <see cref="JsonTextReader.LineNumber"/> and <see cref="JsonTextReader.LinePosition"/> can be provided; otherwise, <c>false</c>.
/// </returns>
public bool HasLineInfo ( )
{
return true ;
}
/// <summary>
/// Gets the current line number.
/// </summary>
/// <value>
/// The current line number or 0 if no line information is available (for example, <see cref="JsonTextReader.HasLineInfo"/> returns <c>false</c>).
/// </value>
public int LineNumber
{
get
{
if ( CurrentState = = State . Start & & LinePosition = = 0 & & TokenType ! = JsonToken . Comment )
{
return 0 ;
}
return _lineNumber ;
}
}
/// <summary>
/// Gets the current line position.
/// </summary>
/// <value>
/// The current line position or 0 if no line information is available (for example, <see cref="JsonTextReader.HasLineInfo"/> returns <c>false</c>).
/// </value>
public int LinePosition = > _charPos - _lineStartPos ;
}
}