Exemplo n.º 1
0
        /// <summary>
        /// Parses a property name and the colon after it.
        /// </summary>
        /// <returns>The node type to report to the user.</returns>
        private JsonNodeType ParseProperty()
        {
            // Increase the count of values under the object (the number of properties).
            Debug.Assert(this.scopes.Count >= 1 && this.scopes.Peek().Type == ScopeType.Object, "Property can only occur in an object.");
            this.scopes.Peek().ValueCount++;

            this.PushScope(ScopeType.Property);

            // Parse the name of the property
            this.nodeValue = this.ParseName();

            if (string.IsNullOrEmpty((string)this.nodeValue))
            {
                // The name can't be empty.
                throw JsonReaderExtensions.CreateException(Strings.JsonReader_InvalidPropertyNameOrUnexpectedComma((string)this.nodeValue));
            }

            if (!this.SkipWhitespaces() || this.characterBuffer[this.tokenStartIndex] != ':')
            {
                // We need the colon character after the property name
                throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingColon((string)this.nodeValue));
            }

            // Consume the colon.
            Debug.Assert(this.characterBuffer[this.tokenStartIndex] == ':', "The above should verify that there's a colon.");
            this.tokenStartIndex++;

            return(JsonNodeType.Property);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Parses a "value", that is an array, object or primitive value.
        /// </summary>
        /// <returns>The node type to report to the user.</returns>
        private JsonNodeType ParseValue()
        {
            Debug.Assert(
                this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]),
                "The SkipWhitespaces wasn't called or it didn't correctly skip all whitespace characters from the input.");
            Debug.Assert(this.scopes.Count >= 1 && this.scopes.Peek().Type != ScopeType.Object, "Value can only occure at the root, in array or as a property value.");

            // Increase the count of values under the current scope.
            this.scopes.Peek().ValueCount++;

            char currentCharacter = this.characterBuffer[this.tokenStartIndex];

            switch (currentCharacter)
            {
            case '{':
                // Start of object
                this.PushScope(ScopeType.Object);
                this.tokenStartIndex++;
                return(JsonNodeType.StartObject);

            case '[':
                // Start of array
                this.PushScope(ScopeType.Array);
                this.tokenStartIndex++;
                return(JsonNodeType.StartArray);

            case '"':
            case '\'':
                // String primitive value
                bool hasLeadingBackslash;
                this.nodeValue = this.ParseStringPrimitiveValue(out hasLeadingBackslash);
                break;

            case 'n':
                this.nodeValue = this.ParseNullPrimitiveValue();
                break;

            case 't':
            case 'f':
                this.nodeValue = this.ParseBooleanPrimitiveValue();
                break;

            default:
                // COMPAT 47: JSON number can start with dot.
                // The JSON spec doesn't allow numbers to start with ., but WCF DS does. We will follow the WCF DS behavior for compatibility.
                if (Char.IsDigit(currentCharacter) || (currentCharacter == '-') || (currentCharacter == '.'))
                {
                    this.nodeValue = this.ParseNumberPrimitiveValue();
                    break;
                }
                else
                {
                    // Unknown token - fail.
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedToken);
                }
            }

            this.TryPopPropertyScope();
            return(JsonNodeType.PrimitiveValue);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Skips over a JSON value (primitive, object or array).
        /// </summary>
        /// <param name="jsonReader">The <see cref="JsonReader"/> to read from.</param>
        /// <remarks>
        /// Pre-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.StartArray or JsonNodeType.StartObject
        /// Post-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.EndArray or JsonNodeType.EndObject
        /// </remarks>
        internal static void SkipValue(this IJsonReader jsonReader)
        {
            Debug.Assert(jsonReader != null, "jsonReader != null");
            int depth = 0;

            do
            {
                switch (jsonReader.NodeType)
                {
                case JsonNodeType.StartArray:
                case JsonNodeType.StartObject:
                    depth++;
                    break;

                case JsonNodeType.EndArray:
                case JsonNodeType.EndObject:
                    Debug.Assert(depth > 0, "Seen too many scope ends.");
                    depth--;
                    break;

                default:
                    Debug.Assert(
                        jsonReader.NodeType != JsonNodeType.EndOfInput,
                        "We should not have reached end of input, since the scopes should be well formed. Otherwise JsonReader should have failed by now.");
                    break;
                }
            }while (jsonReader.Read() && depth > 0);

            if (depth > 0)
            {
                // Not all open scopes were closed:
                // "Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed."
                throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope);
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Parses the number primitive values.
        /// </summary>
        /// <returns>Parse value to Int32, Decimal or Double. Otherwise throws.</returns>
        /// <remarks>Assumes that the current token position points to the first character of the number, so either digit, dot or dash.</remarks>
        private object ParseNumberPrimitiveValue()
        {
            Debug.Assert(
                this.tokenStartIndex < this.storedCharacterCount && (this.characterBuffer[this.tokenStartIndex] == '.' || this.characterBuffer[this.tokenStartIndex] == '-' || Char.IsDigit(this.characterBuffer[this.tokenStartIndex])),
                "The method should only be called when a digit, dash or dot character is the start of the token.");

            // Walk over all characters which might belong to the number
            // Skip the first one since we already verified it belongs to the number.
            int currentCharacterTokenRelativeIndex = 1;

            while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput())
            {
                char character = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex];
                if (Char.IsDigit(character) ||
                    (character == '.') ||
                    (character == 'E') ||
                    (character == 'e') ||
                    (character == '-') ||
                    (character == '+'))
                {
                    currentCharacterTokenRelativeIndex++;
                }
                else
                {
                    break;
                }
            }

            // We now have all the characters which belong to the number, consume it into a string.
            string  numberString = this.ConsumeTokenToString(currentCharacterTokenRelativeIndex);
            double  doubleValue;
            int     intValue;
            decimal decimalValue;

            // We will first try and convert the value to Int32. If it succeeds, use that.
            // And then, we will try Decimal, since it will lose precision while expected type is specified.
            // Otherwise, we will try and convert the value into a double.
            if (Int32.TryParse(numberString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out intValue))
            {
                return(intValue);
            }

            // if it is not Ieee754Compatible, decimal will be parsed before double to keep precision
            if (!isIeee754Compatible && Decimal.TryParse(numberString, NumberStyles.Number, NumberFormatInfo.InvariantInfo, out decimalValue))
            {
                return(decimalValue);
            }

            if (Double.TryParse(numberString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue))
            {
                return(doubleValue);
            }

            throw JsonReaderExtensions.CreateException(Strings.JsonReader_InvalidNumberFormat(numberString));
        }
Exemplo n.º 5
0
        /// <summary>
        /// Parses the null primitive value.
        /// </summary>
        /// <returns>Always returns null if successful. Otherwise throws.</returns>
        /// <remarks>Assumes that the current token position points to the 'n' character.</remarks>
        private object ParseNullPrimitiveValue()
        {
            Debug.Assert(
                this.tokenStartIndex < this.storedCharacterCount && this.characterBuffer[this.tokenStartIndex] == 'n',
                "The method should only be called when the 'n' character is the start of the token.");

            // We can call ParseName since we know the first character is 'n' and thus it won't be quoted.
            string token = this.ParseName();

            if (!string.Equals(token, JsonConstants.JsonNullLiteral, StringComparison.Ordinal))
            {
                throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedToken(token));
            }

            return(null);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Called when end of input is reached.
        /// </summary>
        /// <returns>Always returns false, used for easy readability of the callers.</returns>
        private bool EndOfInput()
        {
            // We should be ending the input only with Root in the scope.
            if (this.scopes.Count > 1)
            {
                // Not all open scopes were closed.
                throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope);
            }

            Debug.Assert(
                this.scopes.Count > 0 && this.scopes.Peek().Type == ScopeType.Root && this.scopes.Peek().ValueCount <= 1,
                "The end of input should only occure with root at the top of the stack with zero or one value.");
            Debug.Assert(this.nodeValue == null, "The node value should have been reset to null.");

            this.nodeType = JsonNodeType.EndOfInput;
            return(false);
        }
Exemplo n.º 7
0
        /// <summary>
        /// Parses the true or false primitive values.
        /// </summary>
        /// <returns>true of false boolean value if successful. Otherwise throws.</returns>
        /// <remarks>Assumes that the current token position points to the 't' or 'f' character.</remarks>
        private object ParseBooleanPrimitiveValue()
        {
            Debug.Assert(
                this.tokenStartIndex < this.storedCharacterCount && (this.characterBuffer[this.tokenStartIndex] == 't' || this.characterBuffer[this.tokenStartIndex] == 'f'),
                "The method should only be called when the 't' or 'f' character is the start of the token.");

            // We can call ParseName since we know the first character is 't' or 'f' and thus it won't be quoted.
            string token = this.ParseName();

            if (string.Equals(token, JsonConstants.JsonFalseLiteral, StringComparison.Ordinal))
            {
                return(false);
            }

            if (string.Equals(token, JsonConstants.JsonTrueLiteral, StringComparison.Ordinal))
            {
                return(true);
            }

            throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedToken(token));
        }
Exemplo n.º 8
0
        private string ParseStringPrimitiveValue(out bool hasLeadingBackslash)
        {
            Debug.Assert(this.tokenStartIndex < this.storedCharacterCount, "At least the quote must be present.");

            hasLeadingBackslash = false;

            // COMPAT 45: We allow both double and single quotes around a primitive string
            // Even though JSON spec only allows double quotes.
            char openingQuoteCharacter = this.characterBuffer[this.tokenStartIndex];

            Debug.Assert(openingQuoteCharacter == '"' || openingQuoteCharacter == '\'', "The quote character must be the current character when this method is called.");

            // Consume the quote character
            this.tokenStartIndex++;

            // String builder to be used if we need to resolve escape sequences.
            StringBuilder valueBuilder = null;

            int currentCharacterTokenRelativeIndex = 0;

            while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput())
            {
                Debug.Assert((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount, "ReadInput didn't read more data but returned true.");

                char character = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex];
                if (character == '\\')
                {
                    // If we're at the begining of the string
                    // (means that relative token index must be 0 and we must not have consumed anything into our value builder yet)
                    if (currentCharacterTokenRelativeIndex == 0 && valueBuilder == null)
                    {
                        hasLeadingBackslash = true;
                    }

                    // We will need the stringbuilder to resolve the escape sequences.
                    if (valueBuilder == null)
                    {
                        if (this.stringValueBuilder == null)
                        {
                            this.stringValueBuilder = new StringBuilder();
                        }
                        else
                        {
                            this.stringValueBuilder.Length = 0;
                        }

                        valueBuilder = this.stringValueBuilder;
                    }

                    // Append everything up to the \ character to the value.
                    valueBuilder.Append(this.ConsumeTokenToString(currentCharacterTokenRelativeIndex));
                    currentCharacterTokenRelativeIndex = 0;
                    Debug.Assert(this.characterBuffer[this.tokenStartIndex] == '\\', "We should have consumed everything up to the escape character.");

                    // Escape sequence - we need at least two characters, the backslash and the one character after it.
                    if (!this.EnsureAvailableCharacters(2))
                    {
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\"));
                    }

                    // To simplify the code, consume the character after the \ as well, since that is the start of the escape sequence.
                    character             = this.characterBuffer[this.tokenStartIndex + 1];
                    this.tokenStartIndex += 2;

                    switch (character)
                    {
                    case 'b':
                        valueBuilder.Append('\b');
                        break;

                    case 'f':
                        valueBuilder.Append('\f');
                        break;

                    case 'n':
                        valueBuilder.Append('\n');
                        break;

                    case 'r':
                        valueBuilder.Append('\r');
                        break;

                    case 't':
                        valueBuilder.Append('\t');
                        break;

                    case '\\':
                    case '\"':
                    case '\'':
                    case '/':
                        valueBuilder.Append(character);
                        break;

                    case 'u':
                        Debug.Assert(currentCharacterTokenRelativeIndex == 0, "The token should be starting at the first character after the \\u");

                        // We need 4 hex characters
                        if (!this.EnsureAvailableCharacters(4))
                        {
                            throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\uXXXX"));
                        }

                        string unicodeHexValue = this.ConsumeTokenToString(4);
                        int    characterValue;
                        if (!Int32.TryParse(unicodeHexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out characterValue))
                        {
                            throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\u" + unicodeHexValue));
                        }

                        valueBuilder.Append((char)characterValue);
                        break;

                    default:
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\" + character));
                    }
                }
                else if (character == openingQuoteCharacter)
                {
                    // Consume everything up to the quote character
                    string result = this.ConsumeTokenToString(currentCharacterTokenRelativeIndex);
                    Debug.Assert(this.characterBuffer[this.tokenStartIndex] == openingQuoteCharacter, "We should have consumed everything up to the quote character.");

                    // Consume the quote character as well.
                    this.tokenStartIndex++;

                    if (valueBuilder != null)
                    {
                        valueBuilder.Append(result);
                        result = valueBuilder.ToString();
                    }

                    return(result);
                }
                else
                {
                    // Normal character, just skip over it - it will become part of the value as is.
                    currentCharacterTokenRelativeIndex++;
                }
            }

            throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedEndOfString);
        }
Exemplo n.º 9
0
        public virtual bool Read()
        {
            // Reset the node value.
            this.nodeValue = null;

#if DEBUG
            // Reset the node type to None - so that we can verify that the Read method actually sets it.
            this.nodeType = JsonNodeType.None;
#endif

            // Skip any whitespace characters.
            // This also makes sure that we have at least one non-whitespace character available.
            if (!this.SkipWhitespaces())
            {
                return(this.EndOfInput());
            }

            Debug.Assert(
                this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]),
                "The SkipWhitespaces didn't correctly skip all whitespace characters from the input.");

            Scope currentScope = this.scopes.Peek();

            bool commaFound = false;
            if (this.characterBuffer[this.tokenStartIndex] == ',')
            {
                commaFound = true;
                this.tokenStartIndex++;

                // Note that validity of the comma is verified below depending on the current scope.
                // Skip all whitespaces after comma.
                // Note that this causes "Unexpected EOF" error if the comma is the last thing in the input.
                // It might not be the best error message in certain cases, but it's still correct (a JSON payload can never end in comma).
                if (!this.SkipWhitespaces())
                {
                    return(this.EndOfInput());
                }

                Debug.Assert(
                    this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]),
                    "The SkipWhitespaces didn't correctly skip all whitespace characters from the input.");
            }

            switch (currentScope.Type)
            {
            case ScopeType.Root:
                if (commaFound)
                {
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Root));
                }

                if (currentScope.ValueCount > 0)
                {
                    // We already found the top-level value, so fail
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_MultipleTopLevelValues);
                }

                // We expect a "value" - start array, start object or primitive value
                this.nodeType = this.ParseValue();
                break;

            case ScopeType.Array:
                if (commaFound && currentScope.ValueCount == 0)
                {
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Array));
                }

                // We might see end of array here
                if (this.characterBuffer[this.tokenStartIndex] == ']')
                {
                    this.tokenStartIndex++;

                    // End of array is only valid when there was no comma before it.
                    if (commaFound)
                    {
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Array));
                    }

                    this.PopScope();
                    this.nodeType = JsonNodeType.EndArray;
                    break;
                }

                if (!commaFound && currentScope.ValueCount > 0)
                {
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingComma(ScopeType.Array));
                }

                // We expect element which is a "value" - start array, start object or primitive value
                this.nodeType = this.ParseValue();
                break;

            case ScopeType.Object:
                if (commaFound && currentScope.ValueCount == 0)
                {
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Object));
                }

                // We might see end of object here
                if (this.characterBuffer[this.tokenStartIndex] == '}')
                {
                    this.tokenStartIndex++;

                    // End of object is only valid when there was no comma before it.
                    if (commaFound)
                    {
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Object));
                    }

                    this.PopScope();
                    this.nodeType = JsonNodeType.EndObject;
                    break;
                }
                else
                {
                    if (!commaFound && currentScope.ValueCount > 0)
                    {
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingComma(ScopeType.Object));
                    }

                    // We expect a property here
                    this.nodeType = this.ParseProperty();
                    break;
                }

            case ScopeType.Property:
                if (commaFound)
                {
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Property));
                }

                // We expect the property value, which is a "value" - start array, start object or primitive value
                this.nodeType = this.ParseValue();
                break;

            default:
                throw JsonReaderExtensions.CreateException(Strings.General_InternalError(InternalErrorCodes.JsonReader_Read));
            }

            Debug.Assert(
                this.nodeType != JsonNodeType.None && this.nodeType != JsonNodeType.EndOfInput,
                "Read should never go back to None and EndOfInput should be reported by directly returning.");

            return(true);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Reads more characters from the input.
        /// </summary>
        /// <returns>true if more characters are available; false if end of input was reached.</returns>
        /// <remarks>This may move characters in the characterBuffer, so after this is called
        /// all indeces to the characterBuffer are invalid except for tokenStartIndex.</remarks>
        private bool ReadInput()
        {
            Debug.Assert(this.tokenStartIndex >= 0 && this.tokenStartIndex <= this.storedCharacterCount, "The token start is out of stored characters range.");

            if (this.endOfInputReached)
            {
                return(false);
            }

            // initialze the buffer
            if (this.characterBuffer == null)
            {
                this.characterBuffer = BufferUtils.RentFromBuffer(ArrayPool, InitialCharacterBufferSize);
            }

            Debug.Assert(this.storedCharacterCount <= this.characterBuffer.Length, "We can only store as many characters as fit into our buffer.");

            // If the buffer is empty (all characters were consumed from it), just start over.
            if (this.tokenStartIndex == this.storedCharacterCount)
            {
                this.tokenStartIndex      = 0;
                this.storedCharacterCount = 0;
            }
            else if (this.storedCharacterCount == this.characterBuffer.Length)
            {
                // No more room in the buffer, move or grow the buffer.
                if (this.tokenStartIndex < this.characterBuffer.Length / 4) // Tested 1/2, 3/4 and 1/4, it seems 1/4 is better than other two.
                {
                    // The entire buffer is full of unconsumed characters
                    // We need to grow the buffer. Double the size of the buffer.
                    if (this.characterBuffer.Length == int.MaxValue)
                    {
                        throw JsonReaderExtensions.CreateException(Strings.JsonReader_MaxBufferReached);
                    }

                    int newBufferSize = this.characterBuffer.Length * 2;
                    newBufferSize = newBufferSize < 0 ? int.MaxValue : newBufferSize; // maybe overflow

                    char[] newCharacterBuffer = BufferUtils.RentFromBuffer(ArrayPool, newBufferSize);

                    // Copy the existing characters to the new buffer.
                    Array.Copy(this.characterBuffer, this.tokenStartIndex, newCharacterBuffer, 0,
                               this.storedCharacterCount - this.tokenStartIndex);
                    this.storedCharacterCount = this.storedCharacterCount - this.tokenStartIndex;
                    this.tokenStartIndex      = 0;

                    // And switch the buffers
                    BufferUtils.ReturnToBuffer(ArrayPool, this.characterBuffer);
                    this.characterBuffer = newCharacterBuffer;
                }
                else
                {
                    // Some characters were consumed, we can just move them in the buffer
                    // to get more room without allocating.
                    Array.Copy(this.characterBuffer, this.tokenStartIndex, this.characterBuffer, 0,
                               this.storedCharacterCount - this.tokenStartIndex);
                    this.storedCharacterCount -= this.tokenStartIndex;
                    this.tokenStartIndex       = 0;
                }
            }

            Debug.Assert(
                this.storedCharacterCount < this.characterBuffer.Length,
                "We should have more room in the buffer by now.");

            // Read more characters from the input.
            // Use the Read method which returns any character as soon as it's available
            // we don't want to wait for the entire buffer to fill if the input doesn't have
            // the characters ready.
            int readCount = this.reader.Read(
                this.characterBuffer,
                this.storedCharacterCount,
                this.characterBuffer.Length - this.storedCharacterCount);

            if (readCount == 0)
            {
                // No more characters available, end of input.
                this.endOfInputReached = true;
                return(false);
            }

            this.storedCharacterCount += readCount;
            return(true);
        }
Exemplo n.º 11
0
        /// <summary>
        /// Skips over a JSON value (primitive, object or array), and append raw string to StringBuilder.
        /// </summary>
        /// <param name="jsonReader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="jsonRawValueStringBuilder">The StringBuilder to receive JSON raw string.</param>
        internal static void SkipValue(this IJsonReader jsonReader, StringBuilder jsonRawValueStringBuilder)
        {
            Debug.Assert(jsonReader != null, "jsonReader != null");
            using (StringWriter stringWriter = new StringWriter(jsonRawValueStringBuilder, CultureInfo.InvariantCulture))
            {
                JsonWriter jsonWriter = new JsonWriter(stringWriter, isIeee754Compatible: false);
                int        depth      = 0;
                do
                {
                    switch (jsonReader.NodeType)
                    {
                    case JsonNodeType.PrimitiveValue:
                        if (jsonReader.Value == null)
                        {
                            jsonWriter.WriteValue((string)null);
                        }
                        else
                        {
                            jsonWriter.WritePrimitiveValue(jsonReader.Value);
                        }

                        break;

                    case JsonNodeType.StartArray:
                        jsonWriter.StartArrayScope();
                        depth++;
                        break;

                    case JsonNodeType.StartObject:
                        jsonWriter.StartObjectScope();
                        depth++;
                        break;

                    case JsonNodeType.EndArray:
                        jsonWriter.EndArrayScope();
                        Debug.Assert(depth > 0, "Seen too many scope ends.");
                        depth--;
                        break;

                    case JsonNodeType.EndObject:
                        jsonWriter.EndObjectScope();
                        Debug.Assert(depth > 0, "Seen too many scope ends.");
                        depth--;
                        break;

                    case JsonNodeType.Property:
                        jsonWriter.WriteName(jsonReader.GetPropertyName());
                        break;

                    default:
                        Debug.Assert(
                            jsonReader.NodeType != JsonNodeType.EndOfInput,
                            "We should not have reached end of input, since the scopes should be well formed. Otherwise JsonReader should have failed by now.");
                        break;
                    }
                }while (jsonReader.Read() && depth > 0);

                if (depth > 0)
                {
                    // Not all open scopes were closed:
                    // "Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed."
                    throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope);
                }

                jsonWriter.Flush();
            }
        }