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 |
∞ |
|
|
|
|
|
|
|
|