TL;DR:
If you know the JSON number value will be an integer, then use GetInt64
/TryGetInt64
.
- Don't use
GetInt32
or TryGetInt32
unless you know you can.
Otherwise, just use Decimal
, as in:JsonElement.TryGetDecimal
and JsonElement.GetDecimal
.
The .NET Decimal
type can represent practically every JSON number
value with full precision, regardless of if it's an integer number
, e.g. { "value": 1234 }
or a non-integer number
, e.g. { "value": 12.34 }
in the source JSON.
- ...the only exception is JSON numbers outside the range -296 to 296 or with precision beyond 2 / 1028.
- That's 79,228,162,514,264,337,593,543,950,335 and 0.0000000000000000000000000002 respectively.
- It's highly unlikely you'll encounter those kinds of extreme values in a JSON document because even though the JSON spec itself imposes no numeric limits, most JS environments and JSON libraries operate on the assumption that integer numbers never exceed JS's
Number.MAX_SAFE_INTEGER
(that's 253, or 9,007,199,254,740,991).
- ...as for non-integer values: that depends entirely on the application: statistical or scientific computing applications might have JSON documents with huge, but imprecise,
double
values in it - in which case using TryGetDouble
or GetDouble
would be appropriate, so always check with your project requirements first.
In general, avoid using IEEE-754 floating-point types (Single
and Double
) as they cannot accurately and precisely represent certain types of numbers which would prevent round-tripping and cause data loss or corruption (e.g. try doing ( 0.001f + 0.004f ) - 0.001f
).
In detail:
Assuming a JsonElement
that has a ValueKind
of Number
, the JsonElement
's value could have an underlying type of int
, decimal
, double
, etc.
This assumption is incorrect: the JsonElement
type does not store or represent JSON number
values using any specific .NET type: instead the JsonElement
struct is simply a view over the source JsonDocument
's serialized JSON data: so essentially a JsonElement
is really just a (validated) run-of-text over a giant JSON string.
Therefore, when a JsonElement
's ValueKind == JsonValueKind.Number
it just means it wraps a sequence of characters directly representing the serialized JSON number
value, so there is no "underlying" value representation.
...this means that you can use any of the TryGet...
methods to get a .NET numeric value from a JSON number
value: the only thing you need to consider is if the .NET type can validly represent the JSON number
value or not: so...
- If you know the JSON file will always contain non-
null
integer values under MAX_SAFE_INTEGER
then use GetInt64
(no need for TryGetInt64
).
- If you know the JSON file will always contain non-
null
integer values under 232 then use GetInt32
(no need for TryGetInt32
).
- If you're unsure if the number might be
null
or not, but know it will be an integer value, then don't use TryGetInt64
or TryGetInt32
, instead check ValueKind == JsonValueKind.Null
first, and then use GetInt64
or GetInt32
.
- This approach means that non-
null
, non-integer values will cause a runtime exception (which you should want, as it's an unexpected exceptional circumstance) instead of incorrectly assuming that if TryGetInt32
returns false
then the JsonElement
"must" be null
, which is wrong.
- If you're prototyping, using a REPL, or otherwise messing-around or playing fast-and-loose with data validation requirements then using
TryGetDecimal
is okay, I guess.
Here's a program I wrote to compare how the different methods handle different types of source JSON number
values:
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();
}
which gives me these results:
Input 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 |
∞ |
|
|
|
|
|
|
|
|