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



我正在使用返回JsonElement对象的.NET CoreSystem.Text.Json命名空间解析一些JSON数据。

例如,对于 Int32 类型,JsonElement有一个GetInt32(),如果值不是整数,则会以整数形式返回值或引发异常,还有一个TryGetInt32()将分析的值复制到out变量并根据它是否能够正确分析返回 true 或 false。

这同样适用于几乎所有其他基元类型,但由于某种原因,GetBoolean()GetString()没有try...等效项,即使如果无法正确解析值,它们也会引发异常。

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

UPD

不要介意原始答案,TryGet_number_type方法并不像我(我假设您)所期望的那样工作 - 如果您尝试从ValueKind不是Number的元素中获取"number_type",它们会抛出(例如decimal文档)。

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

原答案

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

根据文档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;
}

如果值的ValueKind既不String也不Null,则GetString抛出:

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);
}
}

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

此方法不会分析 JSON 字符串值的内容。

但我仍然感到困惑,直到我在 github 问题中找到一些描述这些方法的评论。以下是该评论的片段(略微省略,**我添加的):

// 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之间的区别。

后者用于指示令牌的ValueKind(数字、字符串、真、假)与预期类型不匹配。前者(FormatException)的使用有点偏离标签,与人们通常的预期不同;而不是因为解析错误*而被抛出(即"1.sg"不是int),而是因为超出范围的错误而被抛出!

如果我们先看一下数字重载,可以更好地理解这一点。非Try变体要么返回一个值,要么抛出以下两个异常之一:如果值不是数字,InvalidOperationException,如果它们不适合,FormatExceptionsGetInt32的文档:

异常

InvalidOperationException

此值的ValueKind不是Number

FormatException

该值不能表示为Int32

将此与引发一个异常的Try变体进行比较 - 如果类型不是数字,则InvalidOperationException- 但如果值不合适,则返回false。从TryGetInt32的文档:

异常

InvalidOperationException

此值的ValueKind不是Number

返回

布尔true该数是否可以表示为Int32;否则,false

在这种情况下,"不适合"意味着该值对于基础类型来说太大/太小,即使用[Try]GetInt32时大于int.MaxValue

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

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

和文档:

异常

InvalidOperationException

此值的ValueKind既不True也不False

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

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

*注意:没有解析错误,因为这些方法实际上并不尝试解析jsonstring令牌中的数字/布尔值,它们只考虑正确令牌类型的值。换句话说,目前不支持带引号的数字/布尔值:

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

目前(2020-05-30)有一个请求添加对此的支持。此时,我们可能会看到TryGet方法的可用性/功能发生变化。

最新更新