簡體   English   中英

查找 JsonElement 值的基礎類型的慣用方法是什么?

[英]What is the idiomatic way to find the underlying type of a JsonElement value?

.NET 6、C# 10、System.Text.Json

假設JsonElementValueKindNumber ,則JsonElement的值可能具有intdecimaldouble等基礎類型。要找到此基礎類型,可以運行一系列測試,例如TryGetInt32()TryGetInt64()TryGetDouble()等。

這是最佳做法,還是有更慣用的方法?

TL;博士:

  • 如果您知道JSON 數字值將是整數,則使用GetInt64 / TryGetInt64

    • 除非您知道可以,否則不要使用GetInt32TryGetInt32
  • 否則,只需使用Decimal ,如: JsonElement.TryGetDecimalJsonElement.GetDecimal

  • .NET Decimal類型幾乎可以以全精度表示每個 JSON number ,無論它是number ,例如{ "value": 1234 }還是非number ,例如源中的{ "value": 12.34 } JSON。

    • ...唯一的例外是 JSON 數字超出范圍 -2 96到 2 96或精度超過 2 / 10 28
      • 分別是 79,228,162,514,264,337,593,543,950,335 和 0.0000000000000000000000000002。
    • 您不太可能在 JSON 文檔中遇到這些極端值,因為即使JSON 規范本身沒有數字限制,大多數 JS 環境和 JSON 庫都在假設整數永遠不會超過 JS 的Number.MAX_SAFE_INTEGER (即 2 53或 9,007,199,254,740,991)。
      • ...至於非整數值:這完全取決於應用程序:統計或科學計算應用程序可能有 JSON 文檔,其中包含巨大但不精確的double精度值 - 在這種情況下,使用TryGetDoubleGetDouble是合適的,所以總是首先檢查您的項目要求。
  • 一般來說,避免使用 IEEE-754 浮點類型( SingleDouble ),因為它們不能准確和精確地表示某些類型的數字,這會防止往返並導致數據丟失或損壞(例如嘗試執行( 0.001f + 0.004f ) - 0.001f )。


詳細地:

假設JsonElementValueKindNumber ,則JsonElement的值可能具有intdecimaldouble等基礎類型。

這個假設是不正確的: JsonElement類型不使用任何特定的 .NET 類型存儲或表示 JSON number值:相反, JsonElement結構只是源JsonDocument的序列化 JSON 數據的視圖:所以本質上JsonElement實際上只是一個 (經過驗證的)在一個巨大的 JSON 字符串上運行的文本。

因此,當JsonElementValueKind == JsonValueKind.Number時,它只是意味着它包裝了一個直接表示序列化 JSON number值的字符序列,因此沒有“基礎”值表示

...這意味着您可以使用任何TryGet...方法從 JSON number中獲取 .NET 數值:您唯一需要考慮的是 .NET 類型是否可以有效地表示 JSON number與否:所以...

  • 如果您知道JSON 文件將始終包含MAX_SAFE_INTEGER下的非null整數值,則使用GetInt64 (不需要TryGetInt64 )。
  • 如果您知道JSON 文件將始終包含 2 32以下的非null整數值,則使用GetInt32 (不需要TryGetInt32 )。
  • 如果您不確定數字是否為null ,但知道它將是一個整數值,則不要使用TryGetInt64TryGetInt32 ,而是先檢查ValueKind == JsonValueKind.Null然后使用GetInt64GetInt32
    • 這種方法意味着非null 、非整數值將導致運行時異常(您應該想要,因為它是一個意外的異常情況),而不是錯誤地假設如果TryGetInt32返回falseJsonElement “必須”為null ,即錯誤的。
  • 如果您正在制作原型、使用 REPL,或者在數據驗證要求方面搞砸或玩得又快又松,那么我想使用TryGetDecimal是可以的。

這是我編寫的一個程序,用於比較不同方法如何處理不同類型的源 JSON number



Test( @"{ ""value"": 0 }"                );
Test( @"{ ""value"": -1 }"               ); // Negative signed integer.
Test( @"{ ""value"": 512 }"              ); 
Test( @"{ ""value"": 1.005 }"            ); // Non-integer value.
Test( @"{ ""value"": 9007199254740992 }" ); // `Number.MAX_SAFE_INTEGER + 1`
Test( @"{ ""value"": 3.7e-5 }"           ); // Small fractional number.
Test( @"{ ""value"": 9.99e300 }"         ); // Outside the range of Decimal and Single, but within Double's range.

static void Test( String json )
{
    JsonDocument doc = JsonDocument.Parse( json );
    
    JsonElement valueProp = doc.RootElement.GetProperty("value");

    valueProp.Dump();
    
    List<( String method, Boolean ok, Object? value )> list = new();

    // Decimal:
    list.Add( ( nameof(valueProp.TryGetDecimal), ok: valueProp.TryGetDecimal( out Decimal dec ), value: dec ) );

    // IEEE-754:
    list.Add( ( nameof(valueProp.TryGetDouble), ok: valueProp.TryGetDouble( out Double dbl ), value: dbl ) );
    list.Add( ( nameof(valueProp.TryGetSingle), ok: valueProp.TryGetSingle( out Single sng ), value: sng ) );

    // Unsigned integers:
    list.Add( ( nameof(valueProp.TryGetUInt64), ok: valueProp.TryGetUInt64( out UInt64 u64 ), value: u64 ) );
    list.Add( ( nameof(valueProp.TryGetUInt32), ok: valueProp.TryGetUInt32( out UInt32 u32 ), value: u32 ) );
    list.Add( ( nameof(valueProp.TryGetUInt16), ok: valueProp.TryGetUInt16( out UInt16 u16 ), value: u16 ) );
    list.Add( ( nameof(valueProp.TryGetByte), ok: valueProp.TryGetByte  ( out Byte   u8  ), value: u8  ) );

    // Signed integers:
    list.Add( ( nameof(valueProp.TryGetInt64), ok: valueProp.TryGetInt64( out Int64 s64 ), value: s64 ) );
    list.Add( ( nameof(valueProp.TryGetInt32), ok: valueProp.TryGetInt32( out Int32 s32 ), value: s32 ) );
    list.Add( ( nameof(valueProp.TryGetInt16), ok: valueProp.TryGetInt16( out Int16 s16 ), value: s16 ) );
    list.Add( ( nameof(valueProp.TryGetSByte), ok: valueProp.TryGetSByte( out SByte s8  ), value: s8  ) );

    list.Dump();
}

這給了我這些結果:


輸入 JSON TryGetDecimal TryGetDouble TryGetSingle TryGetUInt64 TryGetUInt32 TryGetUInt16 TryGetByte TryGetInt64 TryGetInt32 TryGetInt16 TryGetSByte
{ "value": 0 } 0 0 0 0 0 0 0 0 0 0 0
{ "value": -1 } -1 -1 -1 -1 -1 -1 -1
{ "value": 512 } 512 512 512 512 512 512 512 512 512
{ "value": 1.005 } 1.005 1.005 1.005
{ "value": 9007199254740992 } 9007199254740992 9007199254740992 9.007199E+15 9007199254740992 9007199254740992
{ "value": 3.7e-5 } 0.000037 3.7E-05 3.7E-05
{ "value": 9.99e300 } 9.99E+300

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM