Esempio n. 1
0
    private static async ValueTask <JToken> PerformReadJSONTTokenAsync(
        StreamReaderWithResizableBuffer stream,
        CharacterReader reader
        )
    {
        stream.EraseReadBytesFromBuffer();
        // Read first non-whitespace character
        Char  charRead;
        Int32 prevIdx;

        do
        {
            prevIdx  = stream.ReadBytesCount;
            charRead = await reader.ReadNextCharacterAsync(stream);
        } while (Char.IsWhiteSpace(charRead));


        // We know what kind of JToken we will have based on a single character
        JToken  retVal;
        Boolean encounteredContainerEnd;
        Int32   startIdx = stream.ReadBytesCount;
        Int32   curIdx;
        var     encoding = reader.Encoding;

        switch (charRead)
        {
        case ARRAY_END:
        case OBJ_END:
            // This happens only when reading empty array/object, and this is called recursively.
            retVal = null;
            break;

        case ARRAY_START:
            var array = new JArray();
            encounteredContainerEnd = false;
            // Reuse 'retVal' variable since we really need it only at the end of this case block.
            while (!encounteredContainerEnd && (retVal = await PerformReadJSONTTokenAsync(stream, reader)) != null)
            {
                array.Add(retVal);
                // Read next non-whitespace character - it will be either array value delimiter (',') or array end (']')
                charRead = await reader.ReadNextCharacterAsync(stream, c => !Char.IsWhiteSpace(c));

                encounteredContainerEnd = charRead == ARRAY_END;
            }
            retVal = array;
            break;

        case OBJ_START:
            var obj = new JObject();
            encounteredContainerEnd = false;
            String keyStr;
            // Reuse 'retVal' variable since we really need it only at the end of this case block.
            while (!encounteredContainerEnd && (keyStr = await ReadJSONStringAsync(reader, stream, false)) != null)
            {
                // First JToken should be string being the key
                // Skip whitespace and ':'
                charRead = await reader.ReadNextCharacterAsync(stream, c => !Char.IsWhiteSpace( c ) && c != OBJ_KEY_VALUE_DELIM);

                // Unread previously read character
                stream.UnreadBytes(reader.GetByteCount(charRead));
                // Read another JToken, this one will be our value
                retVal = await PerformReadJSONTTokenAsync(stream, reader);

                obj.Add(keyStr, retVal);
                // Read next non-whitespace character - it will be either object value delimiter (','), or object end ('}')
                charRead = await reader.ReadNextCharacterAsync(stream, c => !Char.IsWhiteSpace(c));

                encounteredContainerEnd = charRead == OBJ_END;
            }
            retVal = obj;
            break;

        case STR_START:
            retVal = new JValue(await ReadJSONStringAsync(reader, stream, true));
            break;

        case 't':
            // Boolean true
            // read 'r'
            Validate(await reader.ReadNextCharacterAsync(stream), 'r');
            // read 'u'
            Validate(await reader.ReadNextCharacterAsync(stream), 'u');
            // read 'e'
            Validate(await reader.ReadNextCharacterAsync(stream), 'e');
            retVal = new JValue(true);
            break;

        case 'f':
            //Boolean false
            // read 'a'
            Validate(await reader.ReadNextCharacterAsync(stream), 'a');
            // read 'l'
            Validate(await reader.ReadNextCharacterAsync(stream), 'l');
            // read 's'
            Validate(await reader.ReadNextCharacterAsync(stream), 's');
            // read 'e'
            Validate(await reader.ReadNextCharacterAsync(stream), 'e');
            retVal = new JValue(false);
            break;

        case 'n':
            // null
            // read 'u'
            Validate(await reader.ReadNextCharacterAsync(stream), 'u');
            // read 'l'
            Validate(await reader.ReadNextCharacterAsync(stream), 'l');
            // read 'l'
            Validate(await reader.ReadNextCharacterAsync(stream), 'l');
            retVal = JValue.CreateNull();
            break;

        default:
            // The only possibility is number - or malformed JSON string
            // Read until first non-number-char
            var lastReadChar = await reader.TryReadNextCharacterAsync(stream, c =>
            {
                // TODO this isn't strictly according to spec... But will do for now.
                switch (c)
                {
                case '-':   // Plus is ok after E/e, Minus is ok at beginning of number, and after E/e
                case '+':
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case (Char)NUMBER_DECIMAL:
                case (Char)NUMBER_EXP_LOW:
                case (Char)NUMBER_EXP_UPPER:
                    return(false);

                default:
                    return(true);
                }
            });

            // Unread previously read character, unless we arrived at the end.
            if (lastReadChar.HasValue)
            {
                stream.UnreadBytes(reader.GetByteCount(lastReadChar.Value));
            }


            // If we have '.' or 'e' or 'E' then it is non-integer
            curIdx = prevIdx;
            var endIdx = stream.ReadBytesCount;

            Byte curASCIIByte;
            while (
                curIdx < endIdx &&
                (curASCIIByte = encoding.ReadASCIIByte(stream.Buffer, ref curIdx)) != NUMBER_DECIMAL &&
                curASCIIByte != NUMBER_EXP_LOW &&
                curASCIIByte != NUMBER_EXP_UPPER
                )
            {
                ;
            }
            var isInteger = curIdx >= endIdx;
            var byteCount = endIdx - prevIdx;
            // TODO maybe use Decimal always for non-integers? PgSQL seems to use NUMERIC for non-integers.
            retVal = isInteger ?
                     new JValue(encoding.ParseInt64Textual(stream.Buffer, ref prevIdx, (byteCount / encoding.BytesPerASCIICharacter, true))) :
                     new JValue(Double.Parse(encoding.Encoding.GetString(stream.Buffer, prevIdx, byteCount), System.Globalization.CultureInfo.InvariantCulture.NumberFormat));
            break;
        }

        return(retVal);
    }