#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
#if HAVE_ASYNC
using System;
using System.Globalization;
using System.Threading;
#if HAVE_BIG_INTEGER
using System.Numerics;
#endif
using System.Threading.Tasks;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json
{
public partial class JsonTextReader
{
// It's not safe to perform the async methods here in a derived class as if the synchronous equivalent
// has been overriden then the asychronous method will no longer be doing the same operation
#if HAVE_ASYNC // Double-check this isn't included inappropriately.
private readonly bool _safeAsync;
#endif
///
/// Asynchronously reads the next JSON token from the source.
///
/// The token to monitor for cancellation requests. The default value is .
/// A that represents the asynchronous read. The
/// property returns true if the next token was read successfully; false if there are no more tokens to read.
/// Derived classes must override this method to get asynchronous behaviour. Otherwise it will
/// execute synchronously, returning an already-completed task.
public override Task ReadAsync(CancellationToken cancellationToken = default)
{
return _safeAsync ? DoReadAsync(cancellationToken) : base.ReadAsync(cancellationToken);
}
internal Task DoReadAsync(CancellationToken cancellationToken)
{
EnsureBuffer();
while (true)
{
switch (_currentState)
{
case State.Start:
case State.Property:
case State.Array:
case State.ArrayStart:
case State.Constructor:
case State.ConstructorStart:
return ParseValueAsync(cancellationToken);
case State.Object:
case State.ObjectStart:
return ParseObjectAsync(cancellationToken);
case State.PostValue:
Task task = ParsePostValueAsync(false, cancellationToken);
if (task.IsCompletedSucessfully())
{
if (task.Result)
{
return AsyncUtils.True;
}
}
else
{
return DoReadAsync(task, cancellationToken);
}
break;
case State.Finished:
return ReadFromFinishedAsync(cancellationToken);
default:
throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
}
}
}
private async Task DoReadAsync(Task task, CancellationToken cancellationToken)
{
bool result = await task.ConfigureAwait(false);
if (result)
{
return true;
}
return await DoReadAsync(cancellationToken).ConfigureAwait(false);
}
private async Task ParsePostValueAsync(bool ignoreComments, CancellationToken cancellationToken)
{
while (true)
{
char currentChar = _chars[_charPos];
switch (currentChar)
{
case '\0':
if (_charsUsed == _charPos)
{
if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(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 '/':
await ParseCommentAsync(!ignoreComments, cancellationToken).ConfigureAwait(false);
if (!ignoreComments)
{
return true;
}
break;
case ',':
_charPos++;
// finished parsing
SetStateBasedOnCurrent();
return false;
case ' ':
case StringUtils.Tab:
// eat
_charPos++;
break;
case StringUtils.CarriageReturn:
await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(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 async Task ReadFromFinishedAsync(CancellationToken cancellationToken)
{
if (await EnsureCharsAsync(0, false, cancellationToken).ConfigureAwait(false))
{
await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false);
if (_isEndOfFile)
{
SetToken(JsonToken.None);
return false;
}
if (_chars[_charPos] == '/')
{
await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false);
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;
}
private Task ReadDataAsync(bool append, CancellationToken cancellationToken)
{
return ReadDataAsync(append, 0, cancellationToken);
}
private async Task ReadDataAsync(bool append, int charsRequired, CancellationToken cancellationToken)
{
if (_isEndOfFile)
{
return 0;
}
PrepareBufferForReadData(append, charsRequired);
int charsRead = await _reader.ReadAsync(_chars, _charsUsed, _chars.Length - _charsUsed - 1, cancellationToken).ConfigureAwait(false);
_charsUsed += charsRead;
if (charsRead == 0)
{
_isEndOfFile = true;
}
_chars[_charsUsed] = '\0';
return charsRead;
}
private async Task ParseValueAsync(CancellationToken cancellationToken)
{
while (true)
{
char currentChar = _chars[_charPos];
switch (currentChar)
{
case '\0':
if (_charsUsed == _charPos)
{
if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0)
{
return false;
}
}
else
{
_charPos++;
}
break;
case '"':
case '\'':
await ParseStringAsync(currentChar, ReadType.Read, cancellationToken).ConfigureAwait(false);
return true;
case 't':
await ParseTrueAsync(cancellationToken).ConfigureAwait(false);
return true;
case 'f':
await ParseFalseAsync(cancellationToken).ConfigureAwait(false);
return true;
case 'n':
if (await EnsureCharsAsync(1, true, cancellationToken).ConfigureAwait(false))
{
switch (_chars[_charPos + 1])
{
case 'u':
await ParseNullAsync(cancellationToken).ConfigureAwait(false);
break;
case 'e':
await ParseConstructorAsync(cancellationToken).ConfigureAwait(false);
break;
default:
throw CreateUnexpectedCharacterException(_chars[_charPos]);
}
}
else
{
_charPos++;
throw CreateUnexpectedEndException();
}
return true;
case 'N':
await ParseNumberNaNAsync(ReadType.Read, cancellationToken).ConfigureAwait(false);
return true;
case 'I':
await ParseNumberPositiveInfinityAsync(ReadType.Read, cancellationToken).ConfigureAwait(false);
return true;
case '-':
if (await EnsureCharsAsync(1, true, cancellationToken).ConfigureAwait(false) && _chars[_charPos + 1] == 'I')
{
await ParseNumberNegativeInfinityAsync(ReadType.Read, cancellationToken).ConfigureAwait(false);
}
else
{
await ParseNumberAsync(ReadType.Read, cancellationToken).ConfigureAwait(false);
}
return true;
case '/':
await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false);
return true;
case 'u':
await ParseUndefinedAsync(cancellationToken).ConfigureAwait(false);
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:
await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(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 == '.')
{
await ParseNumberAsync(ReadType.Read, cancellationToken).ConfigureAwait(false);
return true;
}
throw CreateUnexpectedCharacterException(currentChar);
}
}
}
private async Task ReadStringIntoBufferAsync(char quote, CancellationToken cancellationToken)
{
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 (await ReadDataAsync(true, cancellationToken).ConfigureAwait(false) == 0)
{
_charPos = charPos;
throw JsonReaderException.Create(this, "Unterminated string. Expected delimiter: {0}.".FormatWith(CultureInfo.InvariantCulture, quote));
}
}
break;
case '\\':
_charPos = charPos;
if (!await EnsureCharsAsync(0, true, cancellationToken).ConfigureAwait(false))
{
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 = await ParseUnicodeAsync(cancellationToken).ConfigureAwait(false);
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 (await EnsureCharsAsync(2, true, cancellationToken).ConfigureAwait(false) && _chars[_charPos] == '\\' && _chars[_charPos + 1] == 'u')
{
char highSurrogate = writeChar;
_charPos += 2;
writeChar = await ParseUnicodeAsync(cancellationToken).ConfigureAwait(false);
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;
await ProcessCarriageReturnAsync(true, cancellationToken).ConfigureAwait(false);
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 Task ProcessCarriageReturnAsync(bool append, CancellationToken cancellationToken)
{
_charPos++;
Task task = EnsureCharsAsync(1, append, cancellationToken);
if (task.IsCompletedSucessfully())
{
SetNewLine(task.Result);
return AsyncUtils.CompletedTask;
}
return ProcessCarriageReturnAsync(task);
}
private async Task ProcessCarriageReturnAsync(Task task)
{
SetNewLine(await task.ConfigureAwait(false));
}
private async Task ParseUnicodeAsync(CancellationToken cancellationToken)
{
return ConvertUnicode(await EnsureCharsAsync(4, true, cancellationToken).ConfigureAwait(false));
}
private Task EnsureCharsAsync(int relativePosition, bool append, CancellationToken cancellationToken)
{
if (_charPos + relativePosition < _charsUsed)
{
return AsyncUtils.True;
}
if (_isEndOfFile)
{
return AsyncUtils.False;
}
return ReadCharsAsync(relativePosition, append, cancellationToken);
}
private async Task ReadCharsAsync(int relativePosition, bool append, CancellationToken cancellationToken)
{
int charsRequired = _charPos + relativePosition - _charsUsed + 1;
// 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 = await ReadDataAsync(append, charsRequired, cancellationToken).ConfigureAwait(false);
// no more content
if (charsRead == 0)
{
return false;
}
charsRequired -= charsRead;
} while (charsRequired > 0);
return true;
}
private async Task ParseObjectAsync(CancellationToken cancellationToken)
{
while (true)
{
char currentChar = _chars[_charPos];
switch (currentChar)
{
case '\0':
if (_charsUsed == _charPos)
{
if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0)
{
return false;
}
}
else
{
_charPos++;
}
break;
case '}':
SetToken(JsonToken.EndObject);
_charPos++;
return true;
case '/':
await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false);
return true;
case StringUtils.CarriageReturn:
await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(false);
break;
case StringUtils.LineFeed:
ProcessLineFeed();
break;
case ' ':
case StringUtils.Tab:
// eat
_charPos++;
break;
default:
if (char.IsWhiteSpace(currentChar))
{
// eat
_charPos++;
}
else
{
return await ParsePropertyAsync(cancellationToken).ConfigureAwait(false);
}
break;
}
}
}
private async Task ParseCommentAsync(bool setToken, CancellationToken cancellationToken)
{
// should have already parsed / character before reaching this method
_charPos++;
if (!await EnsureCharsAsync(1, false, cancellationToken).ConfigureAwait(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 (await ReadDataAsync(true, cancellationToken).ConfigureAwait(false) == 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 (await EnsureCharsAsync(0, true, cancellationToken).ConfigureAwait(false))
{
if (_chars[_charPos] == '/')
{
EndComment(setToken, initialPosition, _charPos - 1);
_charPos++;
return;
}
}
}
break;
case StringUtils.CarriageReturn:
if (singlelineComment)
{
EndComment(setToken, initialPosition, _charPos);
return;
}
await ProcessCarriageReturnAsync(true, cancellationToken).ConfigureAwait(false);
break;
case StringUtils.LineFeed:
if (singlelineComment)
{
EndComment(setToken, initialPosition, _charPos);
return;
}
ProcessLineFeed();
break;
default:
_charPos++;
break;
}
}
}
private async Task EatWhitespaceAsync(CancellationToken cancellationToken)
{
while (true)
{
char currentChar = _chars[_charPos];
switch (currentChar)
{
case '\0':
if (_charsUsed == _charPos)
{
if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0)
{
return;
}
}
else
{
_charPos++;
}
break;
case StringUtils.CarriageReturn:
await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(false);
break;
case StringUtils.LineFeed:
ProcessLineFeed();
break;
default:
if (currentChar == ' ' || char.IsWhiteSpace(currentChar))
{
_charPos++;
}
else
{
return;
}
break;
}
}
}
private async Task ParseStringAsync(char quote, ReadType readType, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_charPos++;
ShiftBufferIfNeeded();
await ReadStringIntoBufferAsync(quote, cancellationToken).ConfigureAwait(false);
ParseReadString(quote, readType);
}
private async Task MatchValueAsync(string value, CancellationToken cancellationToken)
{
return MatchValue(await EnsureCharsAsync(value.Length - 1, true, cancellationToken).ConfigureAwait(false), value);
}
private async Task MatchValueWithTrailingSeparatorAsync(string value, CancellationToken cancellationToken)
{
// will match value and then move to the next character, checking that it is a separator character
if (!await MatchValueAsync(value, cancellationToken).ConfigureAwait(false))
{
return false;
}
if (!await EnsureCharsAsync(0, false, cancellationToken).ConfigureAwait(false))
{
return true;
}
return IsSeparator(_chars[_charPos]) || _chars[_charPos] == '\0';
}
private async Task MatchAndSetAsync(string value, JsonToken newToken, object tokenValue, CancellationToken cancellationToken)
{
if (await MatchValueWithTrailingSeparatorAsync(value, cancellationToken).ConfigureAwait(false))
{
SetToken(newToken, tokenValue);
}
else
{
throw JsonReaderException.Create(this, "Error parsing " + newToken.ToString().ToLowerInvariant() + " value.");
}
}
private Task ParseTrueAsync(CancellationToken cancellationToken)
{
return MatchAndSetAsync(JsonConvert.True, JsonToken.Boolean, true, cancellationToken);
}
private Task ParseFalseAsync(CancellationToken cancellationToken)
{
return MatchAndSetAsync(JsonConvert.False, JsonToken.Boolean, false, cancellationToken);
}
private Task ParseNullAsync(CancellationToken cancellationToken)
{
return MatchAndSetAsync(JsonConvert.Null, JsonToken.Null, null, cancellationToken);
}
private async Task ParseConstructorAsync(CancellationToken cancellationToken)
{
if (await MatchValueWithTrailingSeparatorAsync("new", cancellationToken).ConfigureAwait(false))
{
await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false);
int initialPosition = _charPos;
int endPosition;
while (true)
{
char currentChar = _chars[_charPos];
if (currentChar == '\0')
{
if (_charsUsed == _charPos)
{
if (await ReadDataAsync(true, cancellationToken).ConfigureAwait(false) == 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;
await ProcessCarriageReturnAsync(true, cancellationToken).ConfigureAwait(false);
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();
await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false);
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 async Task