Pretty straightforward question. Here is a minimal reproducible example which fails to deserialize Side, which is a readonly struct. I want to keep it struct. I know it's possible to make it work with an enum.
The JSON value could not be converted to Issue.Side. Path: $.S | LineNumber: 0 | BytePositionInLine: 107.
{"e":"executionReport","E":1656055862174,"s":"BTCUSDT","c":"electron_e18ae830a5eb47cc86fb3c64499","S":"BUY","o":"LIMIT","f":"GTC","q":"0.00105000","p":"19000.00000000","P":"0.00000000","F":"0.00000000","g":-1,"C":"","x":"NEW","X":"NEW","r":"NONE","i":11208653996,"l":"0.00000000","z":"0.00000000","L":"0.00000000","n":"0","N":null,"T":1656055862174,"t":-1,"I":23815394070,"w":true,"m":false,"M":false,"O":1656055862174,"Z":"0.00000000","Y":"0.00000000","Q":"0.00000000"}
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Issue;
public readonly struct Side
{
    private Side(string value)
    {
        Value = value;
    }
    public static Side Buy => new("BUY");
    public static Side Sell => new("SELL");
    public string Value { get; }
    public static implicit operator string(Side enm)
    {
        return enm.Value;
    }
    public override string ToString()
    {
        return Value;
    }
}
public record BinanceStreamOrderUpdate
{
    [JsonPropertyName("S")] public Side Side { get; init; }
    [JsonPropertyName("q")] public decimal Quantity { get; init; }
}
public class Program
{
    private static void Main()
    {
        var serializerOptions = new JsonSerializerOptions
        {
            NumberHandling = JsonNumberHandling.AllowReadingFromString
        };
        var json = "{\"e\":\"executionReport\",\"E\":1656055862174,\"s\":\"BTCUSDT\",\"c\":\"electron_e18ae830a5eb47cc86fb3c64499\",\"S\":\"BUY\",\"o\":\"LIMIT\",\"f\":\"GTC\",\"q\":\"0.00105000\",\"p\":\"19000.00000000\",\"P\":\"0.00000000\",\"F\":\"0.00000000\",\"g\":-1,\"C\":\"\",\"x\":\"NEW\",\"X\":\"NEW\",\"r\":\"NONE\",\"i\":11208653996,\"l\":\"0.00000000\",\"z\":\"0.00000000\",\"L\":\"0.00000000\",\"n\":\"0\",\"N\":null,\"T\":1656055862174,\"t\":-1,\"I\":23815394070,\"w\":true,\"m\":false,\"M\":false,\"O\":1656055862174,\"Z\":\"0.00000000\",\"Y\":\"0.00000000\",\"Q\":\"0.00000000\"}}";
        var deserialized = JsonSerializer.Deserialize<BinanceStreamOrderUpdate>(json, serializerOptions);
        Console.ReadLine();
    }
}
Example how people did it using Json.NET
internal class OrderSideConverter : BaseConverter<OrderSide>
{
    public OrderSideConverter(): this(true) { }
    public OrderSideConverter(bool quotes) : base(quotes) { }
    protected override List<KeyValuePair<OrderSide, string>> Mapping => new List<KeyValuePair<OrderSide, string>>
    {
        new KeyValuePair<OrderSide, string>(OrderSide.Buy, "BUY"),
        new KeyValuePair<OrderSide, string>(OrderSide.Sell, "SELL")
    };
}
public abstract class BaseConverter<T>: JsonConverter where T: struct
{
    protected abstract List<KeyValuePair<T, string>> Mapping { get; }
    private readonly bool quotes;
    
    protected BaseConverter(bool useQuotes)
    {
        quotes = useQuotes;
    }
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        var stringValue = value == null? null: GetValue((T) value);
        if (quotes)
            writer.WriteValue(stringValue);
        else
            writer.WriteRawValue(stringValue);
    }
    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        if (reader.Value == null)
            return null;
        var stringValue = reader.Value.ToString();
        if (string.IsNullOrWhiteSpace(stringValue))
            return null;
        if (!GetValue(stringValue, out var result))
        {
            Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {typeof(T)}, Value: {reader.Value}, Known values: {string.Join(", ", Mapping.Select(m => m.Value))}. If you think {reader.Value} should added please open an issue on the Github repo");
            return null;
        }
        return result;
    }
    public T ReadString(string data)
    {
        return Mapping.FirstOrDefault(v => v.Value == data).Key;
    }
    public override bool CanConvert(Type objectType)
    {
        // Check if it is type, or nullable of type
        return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
    }
    private bool GetValue(string value, out T result)
    {
        // Check for exact match first, then if not found fallback to a case insensitive match 
        var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
        if(mapping.Equals(default(KeyValuePair<T, string>)))
            mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
        if (!mapping.Equals(default(KeyValuePair<T, string>)))
        {
            result = mapping.Key;
            return true;
        }
        result = default;
        return false;
    }
    private string GetValue(T value)
    {
        return Mapping.FirstOrDefault(v => v.Key.Equals(value)).Value;
    }
}
