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.Text.Json.Serialization;
public class NullableConverterFactory : JsonConverterFactory
static readonly byte [] Empty = Array.Empty<byte>();
public override bool CanConvert(Type typeToConvert) => Nullable.GetUnderlyingType(typeToConvert) != null;
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) =>
(JsonConverter)Activator.CreateInstance(
typeof(NullableConverter<>).MakeGenericType(
new Type[] { Nullable.GetUnderlyingType(type) }),
BindingFlags.Instance | BindingFlags.Public,
args: new object[] { options },
class NullableConverter<T> : JsonConverter<T?> where T : struct
public NullableConverter(JsonSerializerOptions options) {}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType == JsonTokenType.String)
if (reader.ValueTextEquals(Empty))
return JsonSerializer.Deserialize<T>(ref reader, options);
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.Value, options);
public static void Test()
var options = new JsonSerializerOptions
Converters = { new NullableConverterFactory() },
Assert.AreEqual(null, Newtonsoft.Json.JsonConvert.DeserializeObject<DateTime?>(json));
Assert.AreEqual(null, System.Text.Json.JsonSerializer.Deserialize<DateTime?>(json, options));
Assert.Throws(Is.InstanceOf<Exception>(), () => Newtonsoft.Json.JsonConvert.DeserializeObject<DateTime?>(badJson));
Assert.Throws(Is.InstanceOf<Exception>(), () => System.Text.Json.JsonSerializer.Deserialize<DateTime?>(badJson, options));
TestRoundTrip((DateTime?)DateTime.UtcNow);
TestRoundTrip((DateTime?)null);
TestRoundTrip(DateTime.UtcNow);
TestRoundTrip((int?)null);
TestRoundTrip((decimal?)1.0101010);
TestRoundTrip((decimal?)null);
TestRoundTrip((double?)1.0101010);
TestRoundTrip((double?)null);
Console.WriteLine("Testing deserialization of \"\" to type {0}:", typeof(T));
var options = new JsonSerializerOptions
Converters = { new NullableConverterFactory() },
var i = JsonSerializer.Deserialize<T>("\"\"", options);
Console.WriteLine(" Succeeded, value = {0}", i == null ? "null" : Convert.ToString(i, CultureInfo.InvariantCulture));
Console.WriteLine(" Failed with error \"{0}\"", ex.Message);
static void TestRoundTrip<T>(T value = default)
Console.WriteLine("Testing round-trip of {0} {1}", typeof(T), value);
var options = new JsonSerializerOptions
Converters = { new NullableConverterFactory() },
var json1 = JsonSerializer.Serialize(value);
var json2 = JsonSerializer.Serialize(value, options);
Assert.AreEqual(json1, json2, "json1 == json2");
var value2 = JsonSerializer.Deserialize<T>(json1, options);
Assert.AreEqual(value, value2, "value == value2");
Console.WriteLine(" Passed.");
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(System.Text.Json.JsonSerializer).Assembly.GetName().Name, typeof(System.Text.Json.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];