using System.Text.Json.Serialization;
using System.Collections.Generic;
[JsonOneOf(typeof(ShipmentRefQuery), nameof(ShipmentRefQuery.ShipmentReferenceIds))]
[JsonOneOf(typeof(FcAllocationRefQuery), nameof(FcAllocationRefQuery.FcAllocationReferenceIds))]
[JsonOneOf(typeof(LegacyOrderQuery), nameof(LegacyOrderQuery.LegacyOrderIds))]
public abstract class ShipmentQuery {}
public class ShipmentRefQuery : ShipmentQuery { public Guid[] ShipmentReferenceIds { get; init; } }
public class FcAllocationRefQuery : ShipmentQuery { public Guid[] FcAllocationReferenceIds { get; init; } }
public class LegacyOrderQuery : ShipmentQuery { public int[] LegacyOrderIds { get; init; } }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class JsonOneOfAttribute : Attribute
public Type DerivedType { get; }
public string DiscriminatingPropertyName { get; }
public JsonOneOfAttribute(Type derivedType, string discriminatingPropertyName)
DerivedType = derivedType;
DiscriminatingPropertyName = discriminatingPropertyName;
public class OneOfJsonConverter<TBase> : JsonConverter<TBase> where TBase : class
private readonly Dictionary<string, Type> _discriminatorMap;
public OneOfJsonConverter()
_discriminatorMap = typeof(TBase)
.GetCustomAttributes<JsonOneOfAttribute>()
.ToDictionary(attr => attr.DiscriminatingPropertyName, attr => attr.DerivedType);
public override TBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
using var jsonDoc = JsonDocument.ParseValue(ref reader);
var jsonObject = jsonDoc.RootElement;
foreach (var entry in _discriminatorMap)
if (jsonObject.TryGetProperty(entry.Key, out _))
return (TBase)JsonSerializer.Deserialize(jsonObject, entry.Value, options);
throw new JsonException("Type could not be determined");
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
JsonSerializer.Serialize(writer, value, value.GetType(), options);
public class OneOfJsonConverterFactory : JsonConverterFactory
public override bool CanConvert(Type typeToConvert)
return typeToConvert.GetCustomAttributes<JsonOneOfAttribute>(false).Any();
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
var converterType = typeof(OneOfJsonConverter<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(converterType);
public static void Main()
var options = new JsonSerializerOptions();
options.Converters.Add(new OneOfJsonConverterFactory());
var serialized = "{\"FcAllocationReferenceIds\":[\"b840b54f-815a-414c-b446-39ccfaf2ac12\",\"33afae1a-d8c4-410d-b72b-86ed5051c97f\"]}";
var deserialized = JsonSerializer.Deserialize<ShipmentQuery>(serialized, options);
if (deserialized is FcAllocationRefQuery q)
Console.WriteLine(string.Join(", ", q.FcAllocationReferenceIds));