using System.Text.Json.Serialization;
using static Google.Protobuf.WellKnownTypes.Field.Types;
namespace CoreConnect.Commerce.Messages;
[AttributeUsage(AttributeTargets.Class)]
public sealed class UnionTagAttribute : Attribute
    public String TagPropertyName { get; }
    public UnionTagAttribute(String tagPropertyName) => this.TagPropertyName = tagPropertyName;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class UnionCaseAttribute : Attribute
    public Type CaseType { get; }
    public String TagPropertyValue { get; }
    public UnionCaseAttribute(Type caseType, String tagPropertyValue) =>
        (this.CaseType, this.TagPropertyValue) = (caseType, tagPropertyValue);
public static class ReferenceKind
    public const string Id = "Id";
    public const string Key = "Key";
[UnionCase(typeof(UpdateReference.Key), nameof(ReferenceKind.Key))]
[UnionCase(typeof(UpdateReference.Id), nameof(ReferenceKind.Id))]
[JsonConverter(typeof(UnionConverter<UpdateReference>))]
public abstract record UpdateReference
    private UpdateReference() { }
    public abstract string Kind { get; }
    public record Id(string id) : UpdateReference { public override string Kind { get { return ReferenceKind.Id; } }}
    public record Key(string key) : UpdateReference { public override string Kind { get { return ReferenceKind.Key; } } }
public sealed class UnionConverterFactory : JsonConverterFactory
    public override Boolean CanConvert(Type typeToConvert) =>
        typeToConvert.GetCustomAttribute<UnionTagAttribute>(false) is not null;
    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        var converterType = typeof(UnionConverter<>).MakeGenericType(typeToConvert);
        return (JsonConverter?)Activator.CreateInstance(converterType);
public sealed class UnionConverter<T> : JsonConverter<T> where T : class
    private String TagPropertyName { get; }
    private Dictionary<String, Type> UnionTypes { get; }
        var unionTag = type.GetCustomAttribute<UnionTagAttribute>();
        if (unionTag is null) throw new InvalidOperationException();
        var concreteTypeFactory = type.CreateConcreteTypeFactory();
        this.TagPropertyName = unionTag.TagPropertyName;
        this.UnionTypes = type.GetCustomAttributes<UnionCaseAttribute>()
            .ToDictionary(k => k.TagPropertyValue, e => concreteTypeFactory(e.CaseType));
    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        using var document = JsonDocument.ParseValue(ref reader);
        var propertyName = options.PropertyNamingPolicy?.ConvertName(this.TagPropertyName) ?? this.TagPropertyName;
        var property = document.RootElement.GetProperty(propertyName);
        var type = this.UnionTypes[property.GetString() ?? throw new InvalidOperationException()];
        return (T?)document.ToObject(type, options);
    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer, value, value.GetType(), options);
public static class TypeExtensions
    public static Func<Type, Type> CreateConcreteTypeFactory(this Type type)
            var genericArgs = type.GetGenericArguments();
            return givenType => givenType.MakeGenericType(genericArgs);
            return givenType => givenType;
public static class JsonExtensions
    public static Object? ToObject(this JsonElement element, Type type, JsonSerializerOptions options)
        var bufferWriter = new ArrayBufferWriter<Byte>();
        using (var writer = new Utf8JsonWriter(bufferWriter))
        return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, type, options);
    public static Object? ToObject(this JsonDocument document, Type type, JsonSerializerOptions options)
        if (document is null) throw new ArgumentNullException(nameof(document));
        return document.RootElement.ToObject(type, options);