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

12
我正在使用.NET Core的System.Text.Json命名空间解析一些JSON数据,会返回JsonElement对象。
例如,对于Int32类型,JsonElement有一个GetInt32()方法,将返回该值作为整数或在不是整数时抛出异常,还有一个TryGetInt32()方法,它将解析的值复制到一个out变量中,并根据其是否能正确解析返回true或false。
对于几乎所有其他原始类型都适用相同的规则,但由于某种原因GetBoolean()和GetString()没有try...等效方法,尽管它们也会在无法解析值时抛出异常。
这似乎是一个明显的疏忽,让我觉得我做错了什么。是否有人能解释一下为什么它们不需要?

那是因为 JSON 数据中的每个数据都可以成功转换为字符串,因此不需要使用 TryGetString()。对于布尔数据来说,除非您传递数字零,否则它始终为真。因此我认为不需要提供 TryGetBoolean 方法。 - Rena
2个回答

8

更新

不要在意原始答案,TryGet_number_type 方法并不像我(也许你)期望的那样工作——如果您尝试从 ValueKind 不是 Number 的元素中获取 "number_type",它们将会抛出异常(例如decimal文档)。

所以这个TryGet... API基本上会尝试将内部值解析为某个具体类型,但仅当该值为此尝试的具体类型的有效json type时(对于所有数字类型为Number,对于GuidDateTimeDateTimeOffsetString),否则它将抛出InvalidOperationException,因此没有TryGetStringTryGetBoolean方法是没有意义的(字符串始终是字符串,布尔值始终是布尔值),它们的行为与Get相同。

原始答案:

找不到不使用这些API的任何理由,但自己实现它们不应该是一个大问题(在标准库中有它们仍然很好):

根据文档,当值的ValueKind 不是 TrueFalse 时,GetBoolean 方法会抛出异常。
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抛出异常,如果value的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);
    }
}

谢谢,我现在明白了 - 似乎 "TryGet" 方法没有 C#内置的 "TryParse" 方法尝试得那么彻底,而这正是我所假设的。 - Andy
@Andy 对我来说也是一样的,虽然你可以为这种行为辩护,但在名称中使用“Try”并不是很清晰。 - Guru Stron

7
答案似乎在文档的备注中略有提及(但没有完全解释):

此方法不会解析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(Number、String、True、False)与期望的类型不匹配。前者(FormatException)与通常预期的有点不同;它不是针对解析错误*(即"1.sg"不是int)抛出,而是针对超出范围错误抛出!
如果我们先看数值重载就更容易理解了。非Try变体要么返回一个值,要么抛出两个异常之一:InvalidOperationException如果值不是数字,FormatException如果它们不适合。从GetInt32文档中可以看到: Exceptions

InvalidOperationException

此值的ValueKind不是Number

FormatException

该值无法表示为Int32

与此相比,Try变体将抛出一个异常——如果类型不是数字,则为InvalidOperationException——但如果值不合适,它会返回false。从TryGetInt32文档中可以看到: Exceptions

InvalidOperationException

此值的ValueKind不是Number

Returns

如果该数可以表示为Int32,则返回布尔值true;否则,返回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 变种为我们提供了检测 "是的,这是一个数字,但它超出了适当的范围"。布尔值只有两个可能的值 -- truefalse -- 没有要检测的范围,没有需要区分的 FormatException。类似于 字符串 -- 它要么是一个 string 标记,要么就不是。

重要的是要注意,在所有情况下,如果 ValueKind 不符合方法的预期,则会抛出 InvalidOperationException。如果假设的 TryGetBoolean 遇到 "abc" 的 string 值,它将不会返回 false,而是抛出 InvalidOperationException,因为 ValueKind 不是 TrueFalse。但这已经是 GetBoolean 做的事情了!因此没有必要再添加一个单独的方法。

* 注意:没有解析错误,因为这些方法实际上不会尝试解析 json string 标记内部的数字/布尔值,它们只考虑正确的标记类型的值。换句话说,带引号的数字/布尔值目前不受支持:

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

目前(2020年5月30日),有要求添加支持。届时,我们可能会看到 TryGet 方法的可用性/功能发生变化。


1
我的回答晚了12分钟,因为我一直在手机上打字。它涉及到其他答案提到的一些要点,但更详细一些,所以我选择不删除它。 - pinkfloydx33

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接