#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.Collections.Generic; using System.IO; using System.Globalization; #if HAVE_BIG_INTEGER using System.Numerics; #endif using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json { /// <summary> /// Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. /// </summary> internal abstract partial class JsonReader : IDisposable { /// <summary> /// Specifies the state of the reader. /// </summary> protected internal enum State { /// <summary> /// A <see cref="JsonReader"/> read method has not been called. /// </summary> Start, /// <summary> /// The end of the file has been reached successfully. /// </summary> Complete, /// <summary> /// Reader is at a property. /// </summary> Property, /// <summary> /// Reader is at the start of an object. /// </summary> ObjectStart, /// <summary> /// Reader is in an object. /// </summary> Object, /// <summary> /// Reader is at the start of an array. /// </summary> ArrayStart, /// <summary> /// Reader is in an array. /// </summary> Array, /// <summary> /// The <see cref="JsonReader.Close()"/> method has been called. /// </summary> Closed, /// <summary> /// Reader has just read a value. /// </summary> PostValue, /// <summary> /// Reader is at the start of a constructor. /// </summary> ConstructorStart, /// <summary> /// Reader is in a constructor. /// </summary> Constructor, /// <summary> /// An error occurred that prevents the read operation from continuing. /// </summary> Error, /// <summary> /// The end of the file has been reached successfully. /// </summary> Finished } // current Token data private JsonToken _tokenType; private object _value; internal char _quoteChar; internal State _currentState; private JsonPosition _currentPosition; private CultureInfo _culture; private DateTimeZoneHandling _dateTimeZoneHandling; private int? _maxDepth; private bool _hasExceededMaxDepth; internal DateParseHandling _dateParseHandling; internal FloatParseHandling _floatParseHandling; private string _dateFormatString; private List<JsonPosition> _stack; /// <summary> /// Gets the current reader state. /// </summary> /// <value>The current reader state.</value> protected State CurrentState => _currentState; /// <summary> /// Gets or sets a value indicating whether the source should be closed when this reader is closed. /// </summary> /// <value> /// <c>true</c> to close the source when this reader is closed; otherwise <c>false</c>. The default is <c>true</c>. /// </value> public bool CloseInput { get; set; } /// <summary> /// Gets or sets a value indicating whether multiple pieces of JSON content can /// be read from a continuous stream without erroring. /// </summary> /// <value> /// <c>true</c> to support reading multiple pieces of JSON content; otherwise <c>false</c>. /// The default is <c>false</c>. /// </value> public bool SupportMultipleContent { get; set; } /// <summary> /// Gets the quotation mark character used to enclose the value of a string. /// </summary> public virtual char QuoteChar { get => _quoteChar; protected internal set => _quoteChar = value; } /// <summary> /// Gets or sets how <see cref="DateTime"/> time zones are handled when reading JSON. /// </summary> public DateTimeZoneHandling DateTimeZoneHandling { get => _dateTimeZoneHandling; set { if (value < DateTimeZoneHandling.Local || value > DateTimeZoneHandling.RoundtripKind) { throw new ArgumentOutOfRangeException(nameof(value)); } _dateTimeZoneHandling = value; } } /// <summary> /// Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. /// </summary> public DateParseHandling DateParseHandling { get => _dateParseHandling; set { if (value < DateParseHandling.None || #if !NET20 value > DateParseHandling.DateTimeOffset #else value > DateParseHandling.DateTime #endif ) { throw new ArgumentOutOfRangeException(nameof(value)); } _dateParseHandling = value; } } /// <summary> /// Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. /// </summary> public FloatParseHandling FloatParseHandling { get => _floatParseHandling; set { if (value < FloatParseHandling.Double || value > FloatParseHandling.Decimal) { throw new ArgumentOutOfRangeException(nameof(value)); } _floatParseHandling = value; } } /// <summary> /// Gets or sets how custom date formatted strings are parsed when reading JSON. /// </summary> public string DateFormatString { get => _dateFormatString; set => _dateFormatString = value; } /// <summary> /// Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a <see cref="JsonReaderException"/>. /// </summary> public int? MaxDepth { get => _maxDepth; set { if (value <= 0) { throw new ArgumentException("Value must be positive.", nameof(value)); } _maxDepth = value; } } /// <summary> /// Gets the type of the current JSON token. /// </summary> public virtual JsonToken TokenType => _tokenType; /// <summary> /// Gets the text value of the current JSON token. /// </summary> public virtual object Value => _value; /// <summary> /// Gets the .NET type for the current JSON token. /// </summary> public virtual Type ValueType => _value?.GetType(); /// <summary> /// Gets the depth of the current token in the JSON document. /// </summary> /// <value>The depth of the current token in the JSON document.</value> public virtual int Depth { get { int depth = _stack?.Count ?? 0; if (JsonTokenUtils.IsStartToken(TokenType) || _currentPosition.Type == JsonContainerType.None) { return depth; } else { return depth + 1; } } } /// <summary> /// Gets the path of the current JSON token. /// </summary> public virtual string Path { get { if (_currentPosition.Type == JsonContainerType.None) { return string.Empty; } bool insideContainer = (_currentState != State.ArrayStart && _currentState != State.ConstructorStart && _currentState != State.ObjectStart); JsonPosition? current = insideContainer ? (JsonPosition?)_currentPosition : null; return JsonPosition.BuildPath(_stack, current); } } /// <summary> /// Gets or sets the culture used when reading JSON. Defaults to <see cref="CultureInfo.InvariantCulture"/>. /// </summary> public CultureInfo Culture { get => _culture ?? CultureInfo.InvariantCulture; set => _culture = value; } internal JsonPosition GetPosition(int depth) { if (_stack != null && depth < _stack.Count) { return _stack[depth]; } return _currentPosition; } /// <summary> /// Initializes a new instance of the <see cref="JsonReader"/> class. /// </summary> protected JsonReader() { _currentState = State.Start; _dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; _dateParseHandling = DateParseHandling.DateTime; _floatParseHandling = FloatParseHandling.Double; CloseInput = true; } private void Push(JsonContainerType value) { UpdateScopeWithFinishedValue(); if (_currentPosition.Type == JsonContainerType.None) { _currentPosition = new JsonPosition(value); } else { if (_stack == null) { _stack = new List<JsonPosition>(); } _stack.Add(_currentPosition); _currentPosition = new JsonPosition(value); // this is a little hacky because Depth increases when first property/value is written but only testing here is faster/simpler if (_maxDepth != null && Depth + 1 > _maxDepth && !_hasExceededMaxDepth) { _hasExceededMaxDepth = true; throw JsonReaderException.Create(this, "The reader's MaxDepth of {0} has been exceeded.".FormatWith(CultureInfo.InvariantCulture, _maxDepth)); } } } private JsonContainerType Pop() { JsonPosition oldPosition; if (_stack != null && _stack.Count > 0) { oldPosition = _currentPosition; _currentPosition = _stack[_stack.Count - 1]; _stack.RemoveAt(_stack.Count - 1); } else { oldPosition = _currentPosition; _currentPosition = new JsonPosition(); } if (_maxDepth != null && Depth <= _maxDepth) { _hasExceededMaxDepth = false; } return oldPosition.Type; } private JsonContainerType Peek() { return _currentPosition.Type; } /// <summary> /// Reads the next JSON token from the source. /// </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 abstract bool Read(); /// <summary> /// Reads the next JSON token from the source 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 virtual int? ReadAsInt32() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Integer: case JsonToken.Float: object v = Value; if (v is int i) { return i; } #if HAVE_BIG_INTEGER if (v is BigInteger value) { i = (int)value; } else #endif { try { i = Convert.ToInt32(v, CultureInfo.InvariantCulture); } catch (Exception ex) { // handle error for large integer overflow exceptions throw JsonReaderException.Create(this, "Could not convert to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, v), ex); } } SetToken(JsonToken.Integer, i, false); return i; case JsonToken.String: string s = (string)Value; return ReadInt32String(s); } throw JsonReaderException.Create(this, "Error reading integer. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal int? ReadInt32String(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (int.TryParse(s, NumberStyles.Integer, Culture, out int i)) { SetToken(JsonToken.Integer, i, false); return i; } else { SetToken(JsonToken.String, s, false); throw JsonReaderException.Create(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } } /// <summary> /// Reads the next JSON token from the source 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> public virtual string ReadAsString() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.String: return (string)Value; } if (JsonTokenUtils.IsPrimitiveToken(t)) { object v = Value; if (v != null) { string s; if (v is IFormattable formattable) { s = formattable.ToString(null, Culture); } else { Uri uri = v as Uri; s = uri != null ? uri.OriginalString : v.ToString(); } SetToken(JsonToken.String, s, false); return s; } } throw JsonReaderException.Create(this, "Error reading string. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } /// <summary> /// Reads the next JSON token from the source 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> public virtual byte[] ReadAsBytes() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.StartObject: { ReadIntoWrappedTypeObject(); byte[] data = ReadAsBytes(); 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 JsonToken.String: { // attempt to convert possible base 64 or GUID string to bytes // GUID has to have format 00000000-0000-0000-0000-000000000000 string s = (string)Value; byte[] data; if (s.Length == 0) { data = CollectionUtils.ArrayEmpty<byte>(); } else if (ConvertUtils.TryConvertGuid(s, out Guid g1)) { data = g1.ToByteArray(); } else { data = Convert.FromBase64String(s); } SetToken(JsonToken.Bytes, data, false); return data; } case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Bytes: if (Value is Guid g2) { byte[] data = g2.ToByteArray(); SetToken(JsonToken.Bytes, data, false); return data; } return (byte[])Value; case JsonToken.StartArray: return ReadArrayIntoByteArray(); } throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal byte[] ReadArrayIntoByteArray() { List<byte> buffer = new List<byte>(); while (true) { if (!Read()) { SetToken(JsonToken.None); } if (ReadArrayElementIntoByteArrayReportDone(buffer)) { byte[] d = buffer.ToArray(); SetToken(JsonToken.Bytes, d, false); return d; } } } private bool ReadArrayElementIntoByteArrayReportDone(List<byte> buffer) { switch (TokenType) { case JsonToken.None: throw JsonReaderException.Create(this, "Unexpected end when reading bytes."); case JsonToken.Integer: buffer.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); return false; case JsonToken.EndArray: return true; case JsonToken.Comment: return false; default: throw JsonReaderException.Create(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } } /// <summary> /// Reads the next JSON token from the source 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 virtual double? ReadAsDouble() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Integer: case JsonToken.Float: object v = Value; if (v is double d) { return d; } #if HAVE_BIG_INTEGER if (v is BigInteger value) { d = (double)value; } else #endif { d = Convert.ToDouble(v, CultureInfo.InvariantCulture); } SetToken(JsonToken.Float, d, false); return (double)d; case JsonToken.String: return ReadDoubleString((string)Value); } throw JsonReaderException.Create(this, "Error reading double. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal double? ReadDoubleString(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (double.TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, Culture, out double d)) { SetToken(JsonToken.Float, d, false); return d; } else { SetToken(JsonToken.String, s, false); throw JsonReaderException.Create(this, "Could not convert string to double: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } } /// <summary> /// Reads the next JSON token from the source 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 virtual bool? ReadAsBoolean() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Integer: case JsonToken.Float: 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 JsonToken.String: return ReadBooleanString((string)Value); case JsonToken.Boolean: return (bool)Value; } throw JsonReaderException.Create(this, "Error reading boolean. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal bool? ReadBooleanString(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (bool.TryParse(s, out bool b)) { SetToken(JsonToken.Boolean, b, false); return b; } else { SetToken(JsonToken.String, s, false); throw JsonReaderException.Create(this, "Could not convert string to boolean: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } } /// <summary> /// Reads the next JSON token from the source 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 virtual decimal? ReadAsDecimal() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Integer: case JsonToken.Float: object v = Value; if (v is decimal d) { return d; } #if HAVE_BIG_INTEGER if (v is BigInteger value) { d = (decimal)value; } else #endif { try { d = Convert.ToDecimal(v, CultureInfo.InvariantCulture); } catch (Exception ex) { // handle error for large integer overflow exceptions throw JsonReaderException.Create(this, "Could not convert to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, v), ex); } } SetToken(JsonToken.Float, d, false); return d; case JsonToken.String: return ReadDecimalString((string)Value); } throw JsonReaderException.Create(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal decimal? ReadDecimalString(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (decimal.TryParse(s, NumberStyles.Number, Culture, out decimal d)) { SetToken(JsonToken.Float, d, false); return d; } else { SetToken(JsonToken.String, s, false); throw JsonReaderException.Create(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } } /// <summary> /// Reads the next JSON token from the source 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 virtual DateTime? ReadAsDateTime() { switch (GetContentToken()) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Date: #if !NET20 if (Value is DateTimeOffset offset) { SetToken(JsonToken.Date, offset.DateTime, false); } #endif return (DateTime)Value; case JsonToken.String: string s = (string)Value; return ReadDateTimeString(s); } throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } internal DateTime? ReadDateTimeString(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (DateTimeUtils.TryParseDateTime(s, DateTimeZoneHandling, _dateFormatString, Culture, out DateTime dt)) { dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); SetToken(JsonToken.Date, dt, false); return dt; } if (DateTime.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) { dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); SetToken(JsonToken.Date, dt, false); return dt; } throw JsonReaderException.Create(this, "Could not convert string to DateTime: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } #if !NET20 /// <summary> /// Reads the next JSON token from the source 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 virtual DateTimeOffset? ReadAsDateTimeOffset() { JsonToken t = GetContentToken(); switch (t) { case JsonToken.None: case JsonToken.Null: case JsonToken.EndArray: return null; case JsonToken.Date: if (Value is DateTime time) { SetToken(JsonToken.Date, new DateTimeOffset(time), false); } return (DateTimeOffset)Value; case JsonToken.String: string s = (string)Value; return ReadDateTimeOffsetString(s); default: throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } } internal DateTimeOffset? ReadDateTimeOffsetString(string s) { if (string.IsNullOrEmpty(s)) { SetToken(JsonToken.Null, null, false); return null; } if (DateTimeUtils.TryParseDateTimeOffset(s, _dateFormatString, Culture, out DateTimeOffset dt)) { SetToken(JsonToken.Date, dt, false); return dt; } if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) { SetToken(JsonToken.Date, dt, false); return dt; } SetToken(JsonToken.String, s, false); throw JsonReaderException.Create(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, s)); } #endif internal void ReaderReadAndAssert() { if (!Read()) { throw CreateUnexpectedEndException(); } } internal JsonReaderException CreateUnexpectedEndException() { return JsonReaderException.Create(this, "Unexpected end when reading JSON."); } internal void ReadIntoWrappedTypeObject() { ReaderReadAndAssert(); if (Value != null && Value.ToString() == JsonTypeReflector.TypePropertyName) { ReaderReadAndAssert(); if (Value != null && Value.ToString().StartsWith("System.Byte[]", StringComparison.Ordinal)) { ReaderReadAndAssert(); if (Value.ToString() == JsonTypeReflector.ValuePropertyName) { return; } } } throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); } /// <summary> /// Skips the children of the current token. /// </summary> public void Skip() { if (TokenType == JsonToken.PropertyName) { Read(); } if (JsonTokenUtils.IsStartToken(TokenType)) { int depth = Depth; while (Read() && (depth < Depth)) { } } } /// <summary> /// Sets the current token. /// </summary> /// <param name="newToken">The new token.</param> protected void SetToken(JsonToken newToken) { SetToken(newToken, null, true); } /// <summary> /// Sets the current token and value. /// </summary> /// <param name="newToken">The new token.</param> /// <param name="value">The value.</param> protected void SetToken(JsonToken newToken, object value) { SetToken(newToken, value, true); } /// <summary> /// Sets the current token and value. /// </summary> /// <param name="newToken">The new token.</param> /// <param name="value">The value.</param> /// <param name="updateIndex">A flag indicating whether the position index inside an array should be updated.</param> protected void SetToken(JsonToken newToken, object value, bool updateIndex) { _tokenType = newToken; _value = value; switch (newToken) { case JsonToken.StartObject: _currentState = State.ObjectStart; Push(JsonContainerType.Object); break; case JsonToken.StartArray: _currentState = State.ArrayStart; Push(JsonContainerType.Array); break; case JsonToken.StartConstructor: _currentState = State.ConstructorStart; Push(JsonContainerType.Constructor); break; case JsonToken.EndObject: ValidateEnd(JsonToken.EndObject); break; case JsonToken.EndArray: ValidateEnd(JsonToken.EndArray); break; case JsonToken.EndConstructor: ValidateEnd(JsonToken.EndConstructor); break; case JsonToken.PropertyName: _currentState = State.Property; _currentPosition.PropertyName = (string)value; break; case JsonToken.Undefined: case JsonToken.Integer: case JsonToken.Float: case JsonToken.Boolean: case JsonToken.Null: case JsonToken.Date: case JsonToken.String: case JsonToken.Raw: case JsonToken.Bytes: SetPostValueState(updateIndex); break; } } internal void SetPostValueState(bool updateIndex) { if (Peek() != JsonContainerType.None || SupportMultipleContent) { _currentState = State.PostValue; } else { SetFinished(); } if (updateIndex) { UpdateScopeWithFinishedValue(); } } private void UpdateScopeWithFinishedValue() { if (_currentPosition.HasIndex) { _currentPosition.Position++; } } private void ValidateEnd(JsonToken endToken) { JsonContainerType currentObject = Pop(); if (GetTypeForCloseToken(endToken) != currentObject) { throw JsonReaderException.Create(this, "JsonToken {0} is not valid for closing JsonType {1}.".FormatWith(CultureInfo.InvariantCulture, endToken, currentObject)); } if (Peek() != JsonContainerType.None || SupportMultipleContent) { _currentState = State.PostValue; } else { SetFinished(); } } /// <summary> /// Sets the state based on current token type. /// </summary> protected void SetStateBasedOnCurrent() { JsonContainerType currentObject = Peek(); switch (currentObject) { case JsonContainerType.Object: _currentState = State.Object; break; case JsonContainerType.Array: _currentState = State.Array; break; case JsonContainerType.Constructor: _currentState = State.Constructor; break; case JsonContainerType.None: SetFinished(); break; default: throw JsonReaderException.Create(this, "While setting the reader state back to current object an unexpected JsonType was encountered: {0}".FormatWith(CultureInfo.InvariantCulture, currentObject)); } } private void SetFinished() { _currentState = SupportMultipleContent ? State.Start : State.Finished; } private JsonContainerType GetTypeForCloseToken(JsonToken token) { switch (token) { case JsonToken.EndObject: return JsonContainerType.Object; case JsonToken.EndArray: return JsonContainerType.Array; case JsonToken.EndConstructor: return JsonContainerType.Constructor; default: throw JsonReaderException.Create(this, "Not a valid close JsonToken: {0}".FormatWith(CultureInfo.InvariantCulture, token)); } } void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (_currentState != State.Closed && disposing) { Close(); } } /// <summary> /// Changes the reader's state to <see cref="JsonReader.State.Closed"/>. /// If <see cref="JsonReader.CloseInput"/> is set to <c>true</c>, the source is also closed. /// </summary> public virtual void Close() { _currentState = State.Closed; _tokenType = JsonToken.None; _value = null; } internal void ReadAndAssert() { if (!Read()) { throw JsonSerializationException.Create(this, "Unexpected end when reading JSON."); } } internal void ReadForTypeAndAssert(JsonContract contract, bool hasConverter) { if (!ReadForType(contract, hasConverter)) { throw JsonSerializationException.Create(this, "Unexpected end when reading JSON."); } } internal bool ReadForType(JsonContract contract, bool hasConverter) { // don't read properties with converters as a specific value // the value might be a string which will then get converted which will error if read as date for example if (hasConverter) { return Read(); } ReadType t = contract?.InternalReadType ?? ReadType.Read; switch (t) { case ReadType.Read: return ReadAndMoveToContent(); case ReadType.ReadAsInt32: ReadAsInt32(); break; case ReadType.ReadAsInt64: bool result = ReadAndMoveToContent(); if (TokenType == JsonToken.Undefined) { throw JsonReaderException.Create(this, "An undefined token is not a valid {0}.".FormatWith(CultureInfo.InvariantCulture, contract?.UnderlyingType ?? typeof(long))); } return result; case ReadType.ReadAsDecimal: ReadAsDecimal(); break; case ReadType.ReadAsDouble: ReadAsDouble(); break; case ReadType.ReadAsBytes: ReadAsBytes(); break; case ReadType.ReadAsBoolean: ReadAsBoolean(); break; case ReadType.ReadAsString: ReadAsString(); break; case ReadType.ReadAsDateTime: ReadAsDateTime(); break; #if !NET20 case ReadType.ReadAsDateTimeOffset: ReadAsDateTimeOffset(); break; #endif default: throw new ArgumentOutOfRangeException(); } return (TokenType != JsonToken.None); } internal bool ReadAndMoveToContent() { return Read() && MoveToContent(); } internal bool MoveToContent() { JsonToken t = TokenType; while (t == JsonToken.None || t == JsonToken.Comment) { if (!Read()) { return false; } t = TokenType; } return true; } private JsonToken GetContentToken() { JsonToken t; do { if (!Read()) { SetToken(JsonToken.None); return JsonToken.None; } else { t = TokenType; } } while (t == JsonToken.Comment); return t; } } }