using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Buffers.Text;
using System.Text.Json.Serialization;
public class DoubleConverter : JsonConverter<double>
const double MaxPreciselyRepresentedIntValue = (1L<<49);
public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
if (value < MaxPreciselyRepresentedIntValue && value > -MaxPreciselyRepresentedIntValue)
writer.WriteNumberValue(0.0m + (decimal)value);
Span<byte> utf8bytes = stackalloc byte[32];
if (Utf8Formatter.TryFormat(value, utf8bytes.Slice(0, utf8bytes.Length-2), out var bytesWritten)
&& IsInteger(utf8bytes, bytesWritten))
utf8bytes[bytesWritten++] = (byte)'.';
utf8bytes[bytesWritten++] = (byte)'0';
if (Utf8Parser.TryParse(utf8bytes.Slice(0, bytesWritten), out decimal d, out var _))
writer.WriteNumberValue(d);
if (double.IsFinite(value))
writer.WriteNumberValue(value);
JsonSerializer.Serialize(writer, value, new JsonSerializerOptions { NumberHandling = options.NumberHandling });
static bool IsInteger(Span<byte> utf8bytes, int bytesWritten)
var start = utf8bytes[0] == '-' ? 1 : 0;
for (var i = start; i < bytesWritten; i++)
if (!(utf8bytes[i] >= '0' && utf8bytes[i] <= '9'))
return start < bytesWritten;
public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
public static void Test()
Console.WriteLine($"Math.Log2(1e15)={Math.Log2(1e15)}\n");
(double)decimal.MaxValue,
(double)(0.99m*decimal.MaxValue),
var powersOf2 = Enumerable.Range(0, 64)
.Select(i => 1L<<i).ToList().SelectMany(l => Enumerable.Range(-25, 25).Select(i => (double)((ulong)l + (ulong)i)));
foreach (var d in doubles.Concat(powersOf2))
var defaultOptions = new JsonSerializerOptions
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
var options = new JsonSerializerOptions
Converters = { new DoubleConverter() },
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
var jsonDefault = JsonSerializer.Serialize(d, defaultOptions);
var json = JsonSerializer.Serialize(d, options);
Console.WriteLine("fixed JSON = {0}, Original JSON = {1}", json, jsonDefault);
Assert.AreEqual(Newtonsoft.Json.JsonConvert.SerializeObject(d), json, $"Json.NET and System.Text.Json returned inconsistent results for {d}");
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];