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;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Nodes;
[JsonConverter(typeof(DiscriminatedUnionConverter))]
public struct DiscriminatedUnion<T1, T2>
public DiscriminatedUnion(T1 type1) => (this.value, this.index) = (type1, 1);
public DiscriminatedUnion(T2 type2) => (this.value, this.index) = (type2, 2);
public object? Value => value;
public int Index => index;
public Type? Type => index switch
public static implicit operator DiscriminatedUnion<T1, T2>(T1 value) => new(value);
public static implicit operator DiscriminatedUnion<T1, T2>(T2 value) => new(value);
class DiscriminatedUnionConverter : JsonConverterFactory
public override bool CanConvert(Type typeToConvert) =>
typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DiscriminatedUnion<,>);
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
if (typeToConvert.GetGenericTypeDefinition() == typeof(DiscriminatedUnion<,>))
return (JsonConverter)Activator.CreateInstance(typeof(DiscriminatedUnionConverter<,>).MakeGenericType(typeToConvert.GetGenericArguments()))!;
throw new NotImplementedException(typeToConvert.FullName);
class DiscriminatedUnionConverter<T1, T2> : JsonConverter<DiscriminatedUnion<T1, T2>>
record DTO<T>(int type, T? value);
public override void Write(Utf8JsonWriter writer, DiscriminatedUnion<T1, T2> union, JsonSerializerOptions options)
case 1 : JsonSerializer.Serialize(writer, new DTO<T1>(union.Index, (T1?)union.Value)); break;
case 2 : JsonSerializer.Serialize(writer, new DTO<T2>(union.Index, (T2?)union.Value)); break;
default: JsonSerializer.Serialize(writer, new { type = union.Index }); break;
public override DiscriminatedUnion<T1, T2> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonSerializer.Deserialize<DTO<JsonElement>>(ref reader, options) switch
{ type : 1 } dto => dto.value.Deserialize<T1>(options)!,
{ type : 2 } dto => dto.value.Deserialize<T2>(options)!,
[JsonPropertyName("values")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public DiscriminatedUnion<string?, int []> values { get; set; }
public static void Test()
public static void TestString()
var inputTest = new TestClass { values = "hello" };
var json = JsonSerializer.Serialize(inputTest);
var outputTest = JsonSerializer.Deserialize<TestClass>(json);
switch (outputTest?.values.Value)
case string s : Console.WriteLine($"Value is {s}"); break;
case int [] a : Console.WriteLine($"Value is {string.Join(',', a)}."); break;
Assert.AreEqual(inputTest.values.Value, outputTest?.values.Value);
public static void TestInts()
var options = new JsonSerializerOptions { WriteIndented = true };
var inputTest = new TestClass { values = new int[] {1, 2, 3 }};
var json = JsonSerializer.Serialize(inputTest, options);
var outputTest = JsonSerializer.Deserialize<TestClass>(json);
switch (outputTest?.values.Value)
case string s : Console.WriteLine($"Value is {s}"); break;
case int [] a : Console.WriteLine($"Value is {string.Join(',', a)}."); break;
Assert.AreEqual(inputTest.values.Value, outputTest?.values.Value);
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");