【问题标题】:System.Text.Json parse document that exists internal to a stringSystem.Text.Json 解析存在于字符串内部的文档
【发布时间】:2020-09-24 07:58:39
【问题描述】:

我收到以 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&lt;char&gt; 的解决方案,那就太棒了。

【问题讨论】:

  • 您可以通过CheckAdditionalContent 设置使用json.net 执行此操作,请参阅Discarding garbage characters after json object with Json.Net。不过,system.text.json 中没有内置任何内容来执行此操作。也许 Utf8JsonStreamReaderthis answerParsing a JSON file with .NET core 3.0/System.text.Json 可能会满足您的需求,可能需要适当的调整...
  • 嗯,Utf8JsonStreamReaderthis answerParsing a JSON file with .NET core 3.0/System.text.Json by mtosh 实际上按原样工作!见dotnetfiddle.net/o9Ctba。其实真的没想到。标记为重复,还是添加为答案?
  • 您不能使用 System.Text.JsonReadOnlySpan&lt;char&gt; 反序列化,但您只能从字节跨度、序列或流中反序列化。这是因为它被设计为直接从 Utf8 字节序列反序列化,而不是 char 序列。
  • 这在fiddle 中显示。只需反序列化为JsonElement:jsonStreamReader.Deserialize&lt;JsonElement&gt;(),
  • 我弄清楚了为什么您的测试用例有效:它们都使用有效的 JSON 字符(/ 对 cme​​ts 有效)。如果您添加的测试用例包含对 JSON 无效的字符,例如+_)g,测试失败。 (不知道为什么; 有效。)......也许。 : 也打破了它。

标签: json parsing system.text.json


【解决方案1】:

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

由于我仍然想依靠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&lt;char&gt; 和一个引用的整数。 i 需要是预期 JSON 值的开头,如果找到有效值,它将前进到后面的下一个字符。它还遵循标准的Try* 模式,即返回带有值的输出参数的bool

【讨论】:

    猜你喜欢
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    • 1970-01-01
    • 2021-08-24
    • 1970-01-01
    • 2021-01-17
    • 1970-01-01
    相关资源
    最近更新 更多