为什么 System.Text.Json.JsonElement 没有 TryGetString() 或 TryGetBoolean()

Why doesn't System.Text.Json.JsonElement have TryGetString() or TryGetBoolean()

我正在使用 returns JsonElement 个对象的 .NET Core System.Text.Json 命名空间解析一些 JSON 数据。

对于 Int32 类型,例如,JsonElement 有一个 GetInt32(),它将 return 值作为整数,如果不是整数则抛出异常,并且有也是一个 TryGetInt32(),它将解析的值复制到一个 out 变量,return 的真或假取决于它是否能够正确解析。

这同样适用于几乎所有其他基本类型,但由于某些原因,GetBoolean()GetString() 没有 try... 等价物,即使它们在值不能时也会抛出异常被正确解析。

这似乎是一个明显的疏忽,让我觉得我做错了什么。谁能解释为什么不需要它们?

UPD

不要介意原来的答案,TryGet_number_type 方法并不像我(我假设你)期望的那样工作 - 如果你试图从 "number_type" ValueKind 不是 Number 的元素(例如 decimal docs)。

所以这个 TryGet... API 基本上尝试将内部值解析为某种具体类型,但前提是值对于此尝试的具体类型有效 json typeNumber对于所有数字类型,String 用于 GuidDateTimeDateTimeOffset),否则它会抛出 InvalidOperationException,因此使用 TryGetStringTryGetBoolean 方法导致这里没有歧义(字符串始终是字符串,布尔值始终是布尔值)并且它们的行为与 Get 对应物完全相同。

原回答:

无法找到没有这个 API 的任何原因,但是自己实现它们应该不是什么大问题(将它们放在标准库中仍然很好):

根据 docs GetBoolean 如果值的 ValueKind 既不是 True 也不是 False.

则抛出
public static bool TryGetBoolean(this JsonElement je, out bool parsed)
{
    var (p, r) = je.ValueKind switch
    {
        JsonValueKind.True => (true, true),
        JsonValueKind.False => (false, true),
        _ => (default, false)
    };    
    parsed = p;
    return r;
}

GetString throws 如果值的 ValueKind 既不是 String 也不是 Null:

public static bool TryGetsString(this JsonElement je, out string parsed)
{
    var (p, r) = je.ValueKind switch
    {
        JsonValueKind.String => (je.GetString(), true),
        JsonValueKind.Null => (null, true),
        _ => (default, false)
    };  
    parsed = p;
    return r;
}

和样本测试:

using (JsonDocument document = JsonDocument.Parse(@"{""bool"": true, ""str"": ""string""}"))
{
    if (document.RootElement.GetProperty("bool").TryGetBoolean(out var b))
    {
        Console.WriteLine(b);
    }

    if (document.RootElement.GetProperty("str").TryGetString( out var s))
    {
        Console.WriteLine(s);
    }
}

文档 remarks 中的注释似乎暗示了答案(但未完全解释):

This method does not parse the contents of a JSON string value.

但直到我找到一些 comments in a github issue 描述这些方法之前,我仍然感到困惑。这是该评论的片段(略有删减,** 由我添加):

// InvalidOperationException if Type is not True or False
public bool GetBoolean();

// InvalidOperationException if Type is not Number 
// FormatException if value does not fit 
public decimal GetDecimal(); 
public double GetDouble(); 
public int GetInt32(); 

// InvalidOperationException if Type is not Number
// false if value **does not fit.** 
public bool TryGetDecimal(out decimal value); 
public bool TryGetDouble(out double value); 
public bool TryGetInt32(out int value);

因此,最终归结为 FormatExceptionInvalidOperationException 之间的差异。

后者用于表示token的ValueKind(Number, String, True, False)与预期类型不匹配。前者 (FormatException) 的使用有点偏离人们通常期望的标签;它不是因解析错误*(即“1.sg”不是int)而被抛出,而是因超出范围错误而被抛出

如果我们先看一下数字重载,就可以更好地理解这一点。非 Try 变体 return 一个值或抛出两个异常之一:InvalidOperationException 如果值不是数字,FormatExceptions 如果它们 不适合。来自 GetInt32 的文档:

Exceptions

InvalidOperationException

This value's ValueKind is not Number.

FormatException

The value cannot be represented as an Int32.

将此与抛出一个异常的 Try 变体进行比较 - InvalidOperationException 如果类型不是数字 - 但 return false 如果值不合适。来自 TryGetInt32 的文档:

Exceptions

InvalidOperationException

This value's ValueKind is not Number.

Returns

Boolean true if the number can be represented as an Int32; otherwise, false.

在这种情况下,“不适合”意味着该值对于基础类型来说太 large/small,即。使用 [Try]GetInt32

时大于 int.MaxValue

现在让我们回到 booleans 的情况,您正确地注意到只有一个非 Try 变体。查看同一 github 问题中的评论,我们看到:

// InvalidOperationException if Type is not True or False
public bool GetBoolean();

和文档:

Exceptions

InvalidOperationException

This value's  ValueKind is neither True nor False.

这里缺少的是 FormatException 和“不适合”的情况。正如我们在上面的数字案例中看到的那样,Try 变体让我们检测到“是的,这是一个数字,但它超出了适当的范围”。 Booleans 只有两个可能的值——truefalse——没有检测范围,没有 FormatException 可以区分。与 strings 相似——它要么是 string 标记,要么不是。

需要注意的重要一点是,在所有情况下,如果 ValueKind 与方法预期的不匹配,则会抛出 InvalidOperationException。假设的 TryGetBoolean 不会 return false 如果它遇到 string 值为“abc”,它会抛出 InvalidOperationException 因为 ValueKind 不是 TrueFalse但这已经是 GetBoolean 所做的! 所以不需要单独的方法。

* 注意:没有解析错误,因为这些方法实际上并没有尝试 解析 numbers/bools json string 令牌,他们只考虑正确令牌类型的值。换句话说,目前不支持引用numbers/bools:

{
   "number": 1234,
   "notNumber": "1234",
   "bool": true,
   "notBool": "false"
}

目前 (2020-05-30) request to add support 为此。届时我们可能会看到 TryGet 方法的 availability/functionality 发生变化。