private static async ValueTask <JToken> PerformReadJSONTTokenAsync( MemorizingPotentiallyAsyncReader <Char?, Char> reader ) { reader.ClearBuffer(); // Read first non-whitespace character var charRead = await reader.PeekUntilAsync(c => !Char.IsWhiteSpace(c)); var prevIdx = reader.BufferCount; // We know what kind of JToken we will have based on a single character JToken retVal; Boolean encounteredContainerEnd; switch (charRead) { case Consts.ARRAY_END: case Consts.OBJ_END: // This happens only when reading empty array/object, and this is called recursively. await reader.TryReadNextAsync(); // Consume peeked character retVal = null; break; case Consts.ARRAY_START: await reader.TryReadNextAsync(); // Consume peeked character 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(reader)) != null) { array.Add(retVal); // Read next non-whitespace character - it will be either array value delimiter (',') or array end (']') charRead = await reader.ReadUntilAsync(c => !Char.IsWhiteSpace(c)); encounteredContainerEnd = charRead == Consts.ARRAY_END; } retVal = array; break; case Consts.OBJ_START: await reader.TryReadNextAsync(); // Consume peeked character 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, false)) != null) { // First JToken should be string being the key // Skip whitespace and ':' charRead = await reader.PeekUntilAsync(c => !Char.IsWhiteSpace( c ) && c != Consts.OBJ_KEY_VALUE_DELIM); // Read another JToken, this one will be our value retVal = await PerformReadJSONTTokenAsync(reader); obj.Add(keyStr, retVal); // Read next non-whitespace character - it will be either object value delimiter (','), or object end ('}') charRead = await reader.ReadUntilAsync(c => !Char.IsWhiteSpace(c)); encounteredContainerEnd = charRead == Consts.OBJ_END; } retVal = obj; break; case Consts.STR_START: await reader.TryReadNextAsync(); // Consume peeked character retVal = new JValue(await ReadJSONStringAsync(reader, true)); break; case 't': await reader.TryReadNextAsync(); // Consume peeked character // Boolean true // read 'r' Validate(await reader.ReadNextAsync(), 'r'); // read 'u' Validate(await reader.ReadNextAsync(), 'u'); // read 'e' Validate(await reader.ReadNextAsync(), 'e'); retVal = new JValue(true); break; case 'f': await reader.TryReadNextAsync(); // Consume peeked character //Boolean false // read 'a' Validate(await reader.ReadNextAsync(), 'a'); // read 'l' Validate(await reader.ReadNextAsync(), 'l'); // read 's' Validate(await reader.ReadNextAsync(), 's'); // read 'e' Validate(await reader.ReadNextAsync(), 'e'); retVal = new JValue(false); break; case 'n': await reader.TryReadNextAsync(); // Consume peeked character // null // read 'u' Validate(await reader.ReadNextAsync(), 'u'); // read 'l' Validate(await reader.ReadNextAsync(), 'l'); // read 'l' Validate(await reader.ReadNextAsync(), 'l'); retVal = JValue.CreateNull(); break; default: // The only possibility is number - or malformed JSON string var possibleInteger = await reader.TryParseInt64TextualGenericAsync(); if (possibleInteger.HasValue) { // Check if next character is '.' or 'e' or 'E' var nextChar = await reader.TryPeekAsync(); if (nextChar.HasValue && (nextChar == Consts.NUMBER_DECIMAL || nextChar == Consts.NUMBER_EXP_LOW || nextChar == Consts.NUMBER_EXP_UPPER)) { // This is not a integer, but is a number -> read until first non-number-character await reader.ReadNextAsync(); await reader.PeekUntilAsync(c => !((c >= '0' && c <= '9') || c == Consts.NUMBER_EXP_LOW || c == Consts.NUMBER_EXP_UPPER || c == '-' || c == '+')); // TODO maybe use Decimal always for non-integers? retVal = new JValue(Double.Parse(new String(reader.Buffer, prevIdx, reader.BufferCount - prevIdx), System.Globalization.CultureInfo.InvariantCulture.NumberFormat)); } else { // This is integer retVal = new JValue(possibleInteger.Value); } } else { // Not a number at all retVal = null; } break; } return(retVal); }
private static async ValueTask <String> ReadJSONStringAsync( MemorizingPotentiallyAsyncReader <Char?, Char> reader, Boolean startQuoteRead ) { Char charRead; var proceed = startQuoteRead; if (!proceed) { charRead = await reader.ReadUntilAsync(c => !Char.IsWhiteSpace(c)); proceed = charRead == Consts.STR_START; } String str; if (proceed) { reader.ClearBuffer(); // At this point, we have read the starting quote, now read the contents. async ValueTask <Char> DecodeUnicodeEscape() { Char decoded; var decodeStartIdx = reader.BufferCount; if (await reader.TryReadMore(4) == 4) { var array = reader.Buffer; decoded = (Char)((array[decodeStartIdx].GetHexadecimalValue().GetValueOrDefault() << 12) | (array[decodeStartIdx + 1].GetHexadecimalValue().GetValueOrDefault() << 8) | (array[decodeStartIdx + 2].GetHexadecimalValue().GetValueOrDefault() << 4) | array[decodeStartIdx + 3].GetHexadecimalValue().GetValueOrDefault()); } else { decoded = '\0'; } return(decoded); } // Read string, but mind the escapes // TODO maybe do reader.PeekUntilAsync( c => c == STR_END && reader.Buffer[reader.BufferCount - 2] != STR_ESCAPE ); // And then do escaping in-place...? Int32 curIdx; do { curIdx = reader.BufferCount; charRead = (await reader.TryReadNextAsync()) ?? Consts.STR_END; if (charRead == Consts.STR_ESCAPE_PREFIX) { // Escape handling - next character decides what we will do charRead = (await reader.TryReadNextAsync()) ?? Consts.STR_END; Char replacementByte = '\0'; switch (charRead) { case Consts.STR_END: case Consts.STR_ESCAPE_PREFIX: case '/': // Actual value is just just read char minus the '\' replacementByte = charRead; break; case 'b': // Backspace replacementByte = '\b'; break; case 'f': // Form feed replacementByte = '\f'; break; case 'n': // New line replacementByte = '\n'; break; case 'r': // Carriage return replacementByte = '\r'; break; case 't': // Horizontal tab replacementByte = '\t'; break; case 'u': // Unicode sequence - followed by four hexadecimal digits var decoded = await DecodeUnicodeEscape(); reader.Buffer[curIdx++] = decoded; break; default: // Just let it slide curIdx = reader.BufferCount; break; } if (replacementByte > 0) { // We just read ASCII char, which should be now replaced reader.Buffer[curIdx++] = replacementByte; } // Erase anything extra reader.EraseBufferSegment(curIdx, reader.BufferCount - curIdx); // Always read next char charRead = (Char)0; } } while (charRead != Consts.STR_END); var strCharCount = reader.BufferCount - 1; if (strCharCount <= 0) { str = ""; } else { str = new String(reader.Buffer, 0, strCharCount); } } else { str = null; } return(str); }