Skip to content

Instantly share code, notes, and snippets.

@DamianEdwards
Last active August 24, 2023 17:31
Show Gist options
  • Save DamianEdwards/3f3800a4a2519aebc76a9f426a858e81 to your computer and use it in GitHub Desktop.
Save DamianEdwards/3f3800a4a2519aebc76a9f426a858e81 to your computer and use it in GitHub Desktop.
Auto-sized number parsing with System.Text.Json
using System.Numerics;
using System.Text;
using System.Text.Json;
using System.Text.Unicode;
var json = """
{
"anInt": 123,
"negativeInt": -123,
"biggerInt": 1234567890,
"hugeInt": 123456789012345678901234567890,
"massiveInt": 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890,
"float": 123.456,
"negativeFloat": -123.456,
"exponential": 2.99792458e8,
"negativeExponential": -2.99792458e8,
"bigFloat": 123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890,
"negativeBigFloat": -123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890
}
"""u8;
var dict = new Dictionary<string, object>();
var jsonReader = new Utf8JsonReader(json);
while (jsonReader.Read())
{
if (jsonReader.CurrentDepth == 1) // top-level only
{
if (jsonReader.TokenType == JsonTokenType.PropertyName)
{
var key = jsonReader.GetString();
if (key is not null && jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.Number)
{
var value = jsonReader.GetNumber();
dict[key] = value;
}
}
else
{
break;
}
}
}
}
foreach (var key in dict.Keys)
{
Console.WriteLine($"{key} = {dict[key]} ({dict[key].GetType()})");
}
public static class JsonReaderExtensions
{
public static object GetNumber(this Utf8JsonReader jsonReader)
{
return TryGetNumber(jsonReader, out var number)
? number!
: throw new FormatException($"Could not read JSON number value '{Encoding.UTF8.GetString(jsonReader.ValueSpan)}' as a .NET number type.");
}
public static bool TryGetNumber(this Utf8JsonReader jsonReader, out object? number)
{
// TODO: Optimize by reusing common boxed values
var value = jsonReader.ValueSpan;
if (value.Contains("."u8[0]))
{
// Floating point number
if (jsonReader.TryGetDouble(out var dblValue))
{
number = dblValue switch
{
>= float.MinValue and <= float.MaxValue => (object)(float)dblValue,
_ => dblValue
};
return true;
}
}
else
{
if (jsonReader.TryGetInt64(out var longValue))
{
number = longValue switch
{
>= short.MinValue and <= short.MaxValue => (object)(short)longValue,
>= int.MinValue and <= int.MaxValue => (object)(int)longValue,
_ => longValue
};
return true;
}
else
{
// Possibly too big for int64, try parsing as int128
if (Int128.TryParse(value, out var int128Value))
{
number = int128Value;
return true;
}
if (value.Length <= 256)
{
// Too big for Int128, try BigInteger
// TODO: Handle case where length of digits is more than we want to stackalloc
Span<char> charSpan = stackalloc char[256];
var status = Utf8.ToUtf16(value, charSpan, out var bytesRead, out var charsWritten);
if (status == System.Buffers.OperationStatus.Done && BigInteger.TryParse(charSpan[..charsWritten], out var bigInt))
{
number = bigInt;
return true;
}
}
}
}
number = null;
return false;
}
}
anInt = 123 (System.Int16)
negativeInt = -123 (System.Int16)
biggerInt = 1234567890 (System.Int32)
hugeInt = 123456789012345678901234567890 (System.Int128)
massiveInt = 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 (System.Numerics.BigInteger)
float = 123.456 (System.Single)
negativeFloat = -123.456 (System.Single)
exponential = 299792450 (System.Single)
negativeExponential = -299792450 (System.Single)
bigFloat = 1.2345678901234567E+59 (System.Double)
negativeBigFloat = -1.2345678901234567E+59 (System.Double)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment