private static Json RunParse(TrackedStringManager tracker, ParserRules rules) { Json output = new Json(tracker); try { if (tracker.Text == null) { output.AddError("Null source text provided!"); tracker.HasChanges = false; return(output); } JsonTextIndexer indexer = new JsonTextIndexer(tracker.Text, rules); // TODO: Handle case where root is document without braces( ie "item : value, item2 : value2") int indexOfFirstOpenBracket = indexer.NextAny(0, '{', '['); // ============ No Brackets =========== if (indexOfFirstOpenBracket == -1) { output.Data = new JsonValue(output.TextTracking, 0, indexer.NextAny(0, '}', ']'), false); tracker.HasChanges = false; return(output); } // ============ Has Brackets =========== char nextBracket = output.TextTracking.Text[indexOfFirstOpenBracket]; if (nextBracket == '{') { output.Data = new JsonDocument(indexer, output.TextTracking, indexOfFirstOpenBracket, out int endIndex); tracker.HasChanges = false; return(output); } else // if(nextBracket == '[') { output.Data = new JsonArray(indexer, output.TextTracking, indexOfFirstOpenBracket, out int endIndex); tracker.HasChanges = false; return(output); } } catch (Exception exc) { output.AddError(exc.ToString()); } return(output); }
// ===================[ Construction & Parsing ]=========================== /// <summary> /// Constructor for parsing /// </summary> internal JsonArray(JsonTextIndexer indexer, TrackedStringManager source, int startIndex, out int endIndex) : base(source) { endIndex = -1; int nextEval = indexer.NextAny(startIndex + 1); int lastStart = startIndex + 1; int quoteStart = -1; bool lastWasUseful; while (nextEval != -1) { lastWasUseful = true; switch (source.Text[nextEval]) { case ']': if (quoteStart != -1) { break; } // There can be an unquoted array item inbetween the last comma'd item and the end // bracket. To make sure we handle that case, but don't add empty elements, we're // going to check it's an empty string before we add it. // // While empty keys and values will be supported (keys as long as they are unique) // they must be quoted so we can tell when the item is supposed to end. if (lastStart != nextEval) { this.Add(new JsonValue(source, lastStart, nextEval - 1, false)); } endIndex = nextEval; break; case '[': { int endOfArr; this.Add(new JsonArray(indexer, source, nextEval, out endOfArr)); if (endOfArr == -1) { throw new FormatException($"Invalid json format provided, issue starts with array that begins at text index {nextEval}"); } int peekAheadInd = indexer.NextAny(endOfArr + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } else { nextEval = endOfArr; } } break; case '{': { if (quoteStart != -1) { break; } int endOfDoc; this.Add(new JsonDocument(indexer, source, nextEval, out endOfDoc)); if (endOfDoc == -1) { throw new FormatException($"Invalid json format provided, issue starts with document that begins at text index {nextEval}"); } int peekAheadInd = indexer.NextAny(endOfDoc + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } else { nextEval = endOfDoc; } } break; case ',': if (quoteStart != -1) { break; } // We're safe to assume we can add this here because commas after docs are covered above this.Add(new JsonValue(source, lastStart, nextEval - 1, false)); break; case '\"': if (quoteStart != -1) { this.Add(new JsonValue(source, quoteStart, nextEval - 1, true)); quoteStart = -1; int peekAheadInd = indexer.NextAny(nextEval + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ',') { nextEval = peekAheadInd; } } else { quoteStart = nextEval + 1; } break; default: lastWasUseful = false; break; } if (endIndex != -1) { break; } if (lastWasUseful) { lastStart = nextEval + 1; } nextEval = indexer.NextAny(nextEval + 1); } this.ResetStringValue(startIndex, endIndex); this.Source.Track(this); }
// =========================[ Constructor & Parsing ]=============================== /// <summary> /// Constructor for parsing /// </summary> internal JsonDocument(JsonTextIndexer textIndexer, TrackedStringManager source, int startIndex, out int endIndex) : base(source) { endIndex = -1; int nextEval = textIndexer.NextAny(startIndex + 1); JsonHelper.IndexTrackingHelper keyIndexTracker = new JsonHelper.IndexTrackingHelper(); int lastStart = startIndex + 1; int insideQuoteStart = -1; bool lastWasUseful; while (nextEval != -1) { lastWasUseful = true; switch (source.Text[nextEval]) { case '}': if (insideQuoteStart != -1) { break; } if (keyIndexTracker) { this.Add(keyIndexTracker.CreateTS(source), new JsonValue(source, lastStart, nextEval - 1, false)); } endIndex = nextEval; break; case '{': { if (insideQuoteStart != -1) { break; } int endOfDoc; if (!keyIndexTracker) { throw new FormatException("Can't read json format - error found around offset: " + nextEval.ToString()); } this.Add(keyIndexTracker.CreateTS(source), new JsonDocument(textIndexer, source, nextEval, out endOfDoc)); if (endOfDoc == -1) { throw new FormatException($"Invalid json format provided, issue starts with document that begins at text index {nextEval}"); } nextEval = endOfDoc; keyIndexTracker.Reset(); } break; case '[': { if (insideQuoteStart != -1) { break; } if (!keyIndexTracker) { throw new FormatException("Can't read json format - attempted to add empty string key"); } this.Add(keyIndexTracker.CreateTS(source), new JsonArray(textIndexer, source, nextEval, out int endOfArr)); if (endOfArr == -1) { throw new FormatException($"Invalid json format provided, issue starts with array that begins at text index {nextEval}"); } nextEval = endOfArr; keyIndexTracker.Reset(); } break; case ':': if (insideQuoteStart != -1) { break; } keyIndexTracker.StartIndex = lastStart; keyIndexTracker.EndIndex = nextEval - 1; break; case '\"': // Start quote if (insideQuoteStart == -1) { insideQuoteStart = nextEval + 1; } // End quote else { // Peek ahead so we can decide if it's a key or a value // If the next thing is a colon, then it's a key, otherwise it's a value int peekAheadInd = textIndexer.NextAny(nextEval + 1); if (peekAheadInd != -1 && source.Text[peekAheadInd] == ':') { keyIndexTracker.StartIndex = insideQuoteStart; keyIndexTracker.EndIndex = nextEval - 1; // One before the quote that was just found keyIndexTracker.IsInsideQuotes = true; nextEval = peekAheadInd; } else { if (!keyIndexTracker) { string quote = source.Text.Substring(insideQuoteStart, nextEval - 1); throw new FormatException("Json parsing failed due to incorrect string formatting around string: " + quote); } this.Add(keyIndexTracker.CreateTS(source), new JsonValue(source, insideQuoteStart, nextEval - 1, true)); keyIndexTracker.Reset(); } insideQuoteStart = -1; } break; case ',': if (insideQuoteStart != -1) { break; } if (keyIndexTracker) { var endValue = new JsonValue(source, lastStart, nextEval - 1, false); if (endValue.StringValue.Length != 0) { this.Add(keyIndexTracker.CreateTS(source), endValue); keyIndexTracker.Reset(); } } break; default: lastWasUseful = false; break; } if (endIndex != -1) { break; } if (lastWasUseful) { lastStart = nextEval + 1; } nextEval = textIndexer.NextAny(nextEval + 1); } if (nextEval == -1) { throw new FormatException("Json was improperly formatted"); } this.ResetStringValue(startIndex, endIndex); this.Source.Track(this); }