C# Swappable identifiers

Date: 2025-07-02
public class Identifier : IComparable
{
    private object Value;

    public Identifier(object value)
    {
        Value = value;
    }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
            return false;

        var other = (Identifier)obj;
        return EqualityComparer<object>.Default.Equals(Value, other.Value);
    }
    public object GetValue() => Value;
    public static bool operator ==(Identifier id1, Identifier id2)
    {
        if (ReferenceEquals(id1, id2))
            return true;

        if (id1 is null || id2 is null)
            return false;

        return id1.Equals(id2);
    }
    public static bool operator !=(Identifier id1, Identifier id2) => !(id1 == id2);

    public override int GetHashCode() => Value.GetHashCode();
    public int CompareTo(object other) => InternalCompareTo(other);
    private int InternalCompareTo(object o) => CompareTo(o);

    public static Identifier From(string o) => string.IsNullOrWhiteSpace(o) ? null : new SpecificIdentifier<string>(o);
    public static Identifier From(Guid? o) => (o == null || o == Guid.Empty) ? null : new SpecificIdentifier<Guid>(o.Value);
    public static Identifier From(int? o) => (o == null) ? null : new SpecificIdentifier<int>(o.Value);
    class SpecificIdentifier<T> : Identifier
    {
        public T TypedValue { get; private set; }

        public SpecificIdentifier(T value) : base(value)
        {
            TypedValue = value;
        }
    }
}

public class IdentifierConverter : JsonConverter<Identifier>
{
    public override void WriteJson(JsonWriter writer, Identifier value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value.GetValue());
    }

    public override Identifier ReadJson(JsonReader reader, Type objectType, Identifier existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        if (token is JValue valueWrapper)
        {
            var valueType = valueWrapper.Type;

            return valueType switch
            {
                JTokenType.Integer => Identifier.From((int)valueWrapper.Value),
                JTokenType.Guid => Identifier.From((Guid)valueWrapper.Value),
                JTokenType.String => Identifier.From((string)valueWrapper.Value),
                _ => null
            };
        }
        return null;
    }
}
96160cookie-checkC# Swappable identifiers