System.Text.Json 解析存在于字符串内部的文档

System.Text.Json parse document that exists internal to a string

我收到以 JSON 值(可以是简单的也可以是复杂的)开头的字符串内容,之后还有一些附加内容。我希望能够解析 JSON 文档。

我无法控制该字符串,所以我无法在 JSON 内容后放置任何类型的分隔符来隔离它。

示例:

"true and some more" - yields <true>
"false this is different" - yields <false>
"5.6/7" - yields <5.6>
"\"a string\""; then this" - yields <"a string">
"[null, true]; and some more" - yields <[null, true]>
"{\"key\": \"value\"}, then the end" - yields <{"key": "value"}>

问题是尾随内容。解析器期望输入结束并抛出异常:

')' is invalid after a single JSON value. Expected end of data.

JsonDocumentOptions 中没有允许尾随内容的选项。

作为奖励,如果您能提供使用 ReadOnlySpan<char> 的解决方案,那就太棒了。

自定义 reader 的建议答案对我不起作用,因为基础 reader 中存在问题:它只是不喜欢某些尾随字符。

因为我仍然想依靠 JsonDocument.Parse() 来为我提取元素,所以我真的只需要找到元素停止的位置,将那个位作为一个单独的部分断开,并将其提交给解析方法。这是我想出的:

public static bool TryParseJsonElement(this ReadOnlySpan<char> span, ref int i, out JsonElement element)
{
    try
    {
        int end = i;
        char endChar;
        switch (span[i])
        {
            case 'f':
                end += 5;
                break;
            case 't':
            case 'n':
                end += 4;
                break;
            case '.': case '-': case '0':
            case '1': case '2': case '3':
            case '4': case '5': case '6':
            case '7': case '8': case '9':
                end = i;
                var allowDash = false;
                while (end < span.Length && (span[end].In('0'..'9') ||
                                             span[end].In('e', '.', '-')))
                {
                    if (!allowDash && span[end] == '-') break;
                    allowDash = span[end] == 'e';
                    end++;
                }
                break;
            case '\'':
            case '"':
                end = i + 1;
                endChar = span[i];
                while (end < span.Length && span[end] != endChar)
                {
                    if (span[end] == '\')
                    {
                        end++;
                        if (end >= span.Length) break;
                    }
                    end++;
                }

                end++;
                break;
            case '{':
            case '[':
                end = i + 1;
                endChar = span[i] == '{' ? '}' : ']';
                var inString = false;
                while (end < span.Length)
                {
                    var escaped = false;
                    if (span[end] == '\')
                    {
                        escaped = true;
                        end++;
                        if (end >= span.Length) break;
                    }
                    if (!escaped && span[end] == '"')
                    {
                        inString = !inString;
                    }
                    else if (!inString && span[end] == endChar) break;

                    end++;
                }

                end++;
                break;
            default:
                element = default;
                return false;
        }
        
        var block = span[i..end];
        if (block[0] == '\'' && block[^1] == '\'')
            block = $"\"{block[1..^1].ToString()}\"".AsSpan();
        element = JsonDocument.Parse(block.ToString()).RootElement;
        i = end;
        return true;
    }
    catch
    {
        element = default;
        return false;
    }
}

它不太关心中间的内容,除了(对于字符串、对象和数组)要知道它是否在字符串的中间(对于找到结束字符有效的位置) ) 并检查 \ 分隔的字符。它对我的目的来说效果很好。

它需要一个 ReadOnlySpan<char> 和一个整数作为参考。 i 需要是预期 JSON 值的开始,如果找到有效值,它将前进到之后的下一个字符。它还遵循标准 Try* 模式,即返回带有值输出参数的 bool