internal static JArray Parse(CharReader reader) { // Purpose: Convert a partial string into a JArray // Author : Scott Bakker // Created: 09/13/2019 // LastMod: 08/11/2020 if (reader == null || reader.Peek() == -1) { return(null); } JArray result = new JArray(); JsonRoutines.SkipBOM(reader); JsonRoutines.SkipWhitespace(reader); if (reader.Peek() != '[') { throw new SystemException( $"JSON Error: Unexpected token to start JArray: {reader.Peek()}"); } reader.Read(); do { JsonRoutines.SkipWhitespace(reader); // check for symbols if (reader.Peek() == ']') { reader.Read(); break; // done building JArray } if (reader.Peek() == ',') { // this logic ignores extra commas, but is ok reader.Read(); continue; // next value } if (reader.Peek() == '{') // JObject { JObject jo = JObject.Parse(reader); result.Add(jo); continue; } if (reader.Peek() == '[') // JArray { JArray ja = JArray.Parse(reader); result.Add(ja); continue; } // Get value as a string, convert to object string tempValue = JsonRoutines.GetToken(reader); result.Add(JsonRoutines.JsonValueToObject(tempValue)); } while (true); return(result); }
internal static void SkipBOM(CharReader reader) { // Purpose: Skip over Byte-Order Mark (BOM) at the beginning of a stream // Author : Scott Bakker // Created: 05/20/2020 // LastMod: 08/11/2020 // UTF-8 BOM = 0xEF,0xBB,0xBF = 239,187,191 if (reader.Peek() == '\xEF' && reader.PeekNext() == '\xBB') { reader.Read(); reader.Read(); if (reader.Peek() != '\xBF') { throw new SystemException($"JSON Error: Invalid BOM character: 0x{reader.Peek():X2}"); } reader.Read(); } }
internal static JObject Parse(CharReader reader) { // Purpose: Convert a partial string into a JObject // Author : Scott Bakker // Created: 09/13/2019 // LastMod: 08/11/2020 if (reader == null || reader.Peek() == -1) { return(null); } JObject result = new JObject(); JsonRoutines.SkipBOM(reader); JsonRoutines.SkipWhitespace(reader); if (reader.Peek() != '{') { throw new SystemException( $"JSON Error: Unexpected token to start JObject: {reader.Peek()}"); } reader.Read(); do { JsonRoutines.SkipWhitespace(reader); // check for symbols if (reader.Peek() == '}') { reader.Read(); break; // done building JObject } if (reader.Peek() == ',') { // this logic ignores extra commas, but is ok reader.Read(); continue; // Next key/value } string tempKey = JsonRoutines.GetToken(reader); if (JsonRoutines.IsWhitespaceString(tempKey)) { throw new SystemException(JsonKeyError); } if (tempKey.Length <= 2 || !tempKey.StartsWith("\"") || !tempKey.EndsWith("\"")) { throw new SystemException($"JSON Error: Invalid key format: {tempKey}"); } // Convert to usable key tempKey = JsonRoutines.JsonValueToObject(tempKey).ToString(); if (JsonRoutines.IsWhitespaceString(tempKey.Substring(1, tempKey.Length - 2))) { throw new SystemException(JsonKeyError); } // Check for ":" between key and value JsonRoutines.SkipWhitespace(reader); if (JsonRoutines.GetToken(reader) != ":") { throw new SystemException($"JSON Error: Missing colon: {tempKey}"); } // Get value JsonRoutines.SkipWhitespace(reader); if (reader.Peek() == '{') // JObject { JObject jo = JObject.Parse(reader); result[tempKey] = jo; continue; } if (reader.Peek() == '[') // JArray { JArray ja = JArray.Parse(reader); result[tempKey] = ja; continue; } // Get value as a string, convert to object string tempValue = JsonRoutines.GetToken(reader); result[tempKey] = JsonRoutines.JsonValueToObject(tempValue); } while (true); return(result); }
internal static void SkipWhitespace(CharReader reader) { // Purpose: Skip over any whitespace characters or any recognized comments // Author : Scott Bakker // Created: 09/23/2019 // LastMod: 08/11/2020 // Notes : Comments consist of "/*...*/" or "//" to eol (aka line comment). // : "//" comments don't need an eol if at the end, but "/*" does need "*/". if (reader == null) { return; } bool inComment = false; bool inLineComment = false; while (reader.Peek() != -1) { if (inComment) { if (reader.Peek() == '*' && reader.PeekNext() == '/') // found ending "*/" { inComment = false; reader.Read(); } reader.Read(); continue; } if (inLineComment) { if (reader.Peek() == '\r' || reader.Peek() == '\n') // found end of line { inLineComment = false; } reader.Read(); continue; } if (reader.Peek() == '/' && reader.PeekNext() == '*') { inComment = true; reader.Read(); reader.Read(); continue; } if (reader.Peek() == '/' && reader.PeekNext() == '/') { inLineComment = true; reader.Read(); reader.Read(); continue; } if (IsWhitespace((char)reader.Peek())) { reader.Read(); continue; } break; } if (inComment) { throw new SystemException("JSON Error: Comment starting with /* is not terminated by */"); } }
internal static string GetToken(CharReader reader) { // Purpose: Get a single token from string value for parsing // Author : Scott Bakker // Created: 09/13/2019 // LastMod: 03/09/2021 // Notes : Does not do escaped character expansion here, just passes exact value. // : Properly handles \" within strings this way, but nothing else. if (reader == null || reader.Peek() == -1) { return(null); } char c; // Ignore whitespace before token SkipWhitespace(reader); // Stop if one-character JSON symbol found if (IsJsonSymbol((char)reader.Peek())) { return(((char)reader.Read()).ToString()); } // Have to build token char by char StringBuilder result = new(); bool inQuote = false; bool lastBackslash = false; do { // Peek char for this loop c = (char)reader.Peek(); // Check for whitespace or symbols to end token if (!inQuote) { if (IsWhitespace(c)) { reader.Read(); // gobble char break; // end token } if (IsJsonSymbol(c)) { // don't gobble char break; // end token } // Any comments end the token if (c == '/') { if (reader.PeekNext() == '*' || reader.PeekNext() == '/') { // don't gobble char break; // end token } } if (c != '\"' && !IsJsonValueChar(c)) { throw new SystemException($"JSON Error: Unexpected character: {c}"); } } // Check for escaped chars if (inQuote && lastBackslash) { // Add backslash and character, no expansion here result.Append('\\'); result.Append((char)reader.Read()); lastBackslash = false; } else if (inQuote && c == '\\') { // Remember backslash for next loop, but don't add it to result lastBackslash = true; } else if (c == '\"') { // Check for quotes around a string if (inQuote) { result.Append((char)reader.Read()); // add ending quote inQuote = false; break; // Token is done } if (result.Length > 0) { // Quote in the middle of a token? throw new SystemException("JSON Error: Unexpected quote char"); } result.Append((char)reader.Read()); // add beginning quote inQuote = true; } else { // Add this char result.Append((char)reader.Read()); } }while (reader.Peek() != -1); return(result.ToString()); }