public bool TryParse(string source, ref int index, [NotNullWhen(true)] out JsonValue?value, [NotNullWhen(false)] out string?errorMessage, bool allowExtraChars)
        {
            bool complete = false;

            var obj = new JsonObject();

            value = obj;
            var length = source.Length;

            index++;
            while (index < length)
            {
                errorMessage = source.SkipWhiteSpace(ref index, length, out char c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for empty object
                if (c == '}')
                {
                    if (obj.Count == 0)
                    {
                        complete = true;
                        index++;
                        break;
                    }
                    else
                    {
                        errorMessage = "Expected key.";
                        return(false);
                    }
                }
                // get key
                errorMessage = source.SkipWhiteSpace(ref index, length, out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                if (c != '\"')
                {
                    errorMessage = "Expected key.";
                    return(false);
                }
                if (!JsonParser.TryParse(source, ref index, out var item, out errorMessage))
                {
                    return(false);
                }
                var key = item !.String;
                // check for colon
                errorMessage = source.SkipWhiteSpace(ref index, length, out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                if (c != ':')
                {
                    obj.Add(key, null !);
                    errorMessage = "Expected ':'.";
                    return(false);
                }
                index++;
                // get value (whitespace is removed in Parse)
                var success = JsonParser.TryParse(source, ref index, out item, out errorMessage);
                obj.Add(key, item !);
                if (!success)
                {
                    return(false);
                }
                errorMessage = source.SkipWhiteSpace(ref index, length, out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for end or separator
                index++;
                if (c == '}')
                {
                    complete = true;
                    break;
                }

                if (c != ',')
                {
                    errorMessage = "Expected ','.";
                    return(false);
                }
            }

            if (!complete)
            {
                errorMessage = "Unterminated object (missing '}').";
                return(false);
            }

            errorMessage = null;
            return(true);
        }
        public bool TryParse(TextReader stream, [NotNullWhen(true)] out JsonValue?value, [NotNullWhen(false)] out string?errorMessage)
        {
            Debug.Assert(stream.Peek() == '[');

            bool complete = false;
            var  array    = new JsonArray();

            value = array;
            while (stream.Peek() != -1)
            {
                stream.Read();                 // waste the '[' or ','
                errorMessage = stream.SkipWhiteSpace(out var c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for empty array
                if (c == ']')
                {
                    if (array.Count == 0)
                    {
                        complete = true;
                        stream.Read();                         // waste the ']'
                        break;
                    }

                    errorMessage = "Expected value.";
                    return(false);
                }
                // get value
                var success = JsonParser.TryParse(stream, out var item, out errorMessage);
                array.Add(item !);
                if (!success)
                {
                    return(false);
                }
                errorMessage = stream.SkipWhiteSpace(out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for end or separator
                if (c == ']')
                {
                    complete = true;
                    stream.Read();                     // waste the ']'
                    break;
                }

                if (c != ',')
                {
                    errorMessage = "Expected ','.";
                    return(false);
                }
            }

            if (!complete)
            {
                errorMessage = "Unterminated array (missing ']')";
                return(false);
            }

            errorMessage = null;
            return(true);
        }
        public bool TryParse(TextReader stream, [NotNullWhen(true)] out JsonValue?value, [NotNullWhen(false)] out string?errorMessage)
        {
            bool complete = false;

            var obj = new JsonObject();

            value = obj;
            while (stream.Peek() != -1)
            {
                stream.Read();                 // waste the '{' or ','
                errorMessage = stream.SkipWhiteSpace(out char c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for empty object
                if (c == '}')
                {
                    if (obj.Count == 0)
                    {
                        complete = true;
                        stream.Read();                         // waste the '}'
                        break;
                    }
                    else
                    {
                        errorMessage = "Expected key.";
                        return(false);
                    }
                }
                // get key
                errorMessage = stream.SkipWhiteSpace(out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                if (c != '\"')
                {
                    errorMessage = "Expected key.";
                    return(false);
                }
                if (!JsonParser.TryParse(stream, out var item, out errorMessage))
                {
                    return(false);
                }
                var key = item !.String;
                // check for colon
                errorMessage = stream.SkipWhiteSpace(out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                if (c != ':')
                {
                    obj.Add(key, null !);
                    errorMessage = "Expected ':'.";
                    return(false);
                }
                stream.Read();                 // waste the ':'
                // get value (whitespace is removed in Parse)
                var success = JsonParser.TryParse(stream, out item, out errorMessage);
                obj.Add(key, item !);
                if (!success)
                {
                    return(false);
                }
                errorMessage = stream.SkipWhiteSpace(out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for end or separator
                if (c == '}')
                {
                    complete = true;
                    stream.Read();                     // waste the '}'
                    break;
                }

                if (c != ',')
                {
                    errorMessage = "Expected ','.";
                    return(false);
                }
            }

            if (!complete)
            {
                errorMessage = "Unterminated object (missing '}').";
                return(false);
            }

            errorMessage = null;
            return(true);
        }
        public bool TryParse(string source, ref int index, [NotNullWhen(true)] out JsonValue?value, [NotNullWhen(false)] out string?errorMessage, bool allowExtraChars)
        {
            Debug.Assert(index < source.Length && source[index] == '[');

            var complete = false;
            var array    = new JsonArray();

            value = array;
            var length = source.Length;

            index++;
            while (index < length)
            {
                errorMessage = source.SkipWhiteSpace(ref index, length, out char c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for empty array
                if (c == ']')
                {
                    if (array.Count == 0)
                    {
                        complete = true;
                        index++;
                        break;
                    }

                    errorMessage = "Expected value.";
                    return(false);
                }
                // get value
                var success = JsonParser.TryParse(source, ref index, out var item, out errorMessage);
                array.Add(item !);
                if (!success)
                {
                    return(false);
                }
                errorMessage = source.SkipWhiteSpace(ref index, length, out c);
                if (errorMessage != null)
                {
                    return(false);
                }
                // check for end or separator
                index++;
                if (c == ']')
                {
                    complete = true;
                    break;
                }

                if (c != ',')
                {
                    errorMessage = "Expected ','.";
                    return(false);
                }
            }

            if (!complete)
            {
                errorMessage = "Unterminated array (missing ']')";
                return(false);
            }

            errorMessage = null;
            return(true);
        }