using System.Collections;
using System.Collections.Generic;
using System.Text.Json.Serialization;
[JsonConverter(typeof(OptionalConverter))]
public readonly struct Optional<T>
public bool HasValue { get; }
public static implicit operator Optional<T>(T value) => new Optional<T>(value);
public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified";
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<int?> Prop1 { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<string?> Prop2 { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<long?> Prop3 { get; set; }
public List<Base>? Properties { get; set; }
public class OptionalConverter : JsonConverterFactory
public override bool CanConvert(Type typeToConvert)
if (!typeToConvert.IsGenericType) { return false; }
if (typeToConvert.GetGenericTypeDefinition() != typeof(Optional<>)) { return false; }
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
Type valueType = typeToConvert.GetGenericArguments()[0];
return (JsonConverter)Activator.CreateInstance(
type: typeof(OptionalConverterInner<>).MakeGenericType(new Type[] { valueType }),
bindingAttr: BindingFlags.Instance | BindingFlags.Public,
private class OptionalConverterInner<T> : JsonConverter<Optional<T>>
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
T value = JsonSerializer.Deserialize<T>(ref reader, options);
return new Optional<T>(value);
public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.Value, options);
static Model GetModelInternal(bool baseOrDerived)
var item = baseOrDerived ? new Base { Prop2 = "hello" } : new Base { Prop1 = null, Prop2 = "hello", Prop3 = null };
baseOrDerived ? new Base () : new Base { Prop1 = null, Prop2 = null, Prop3 = null },
public static void Test()
var options = new JsonSerializerOptions
Console.WriteLine("Serialized Model with baseOrDerived = false");
Console.WriteLine(JsonSerializer.Serialize(GetModelInternal(false), options));
Console.WriteLine("Serialized Model with baseOrDerived = true");
Console.WriteLine(JsonSerializer.Serialize(GetModelInternal(true), options));
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: ");