0

System.Text.Json.JsonException: The JSON value could not be converted to Models.Side. Path: $.S | LineNumber: 0 | BytePositionInLine: 107.

JSON

{
    "stream": "c3uwBXY4P4eHD2HNkk5Tmj9SzXKZHRfxMpiWrQsXkvICePRWecoN5gFInj6O",
    "data": {
        "e": "executionReport",
        "E": 1656027820175,
        "s": "BTCUSDT",
        "c": "electron_77d6dd561690469e8fdfe178738",
        "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": 11203182805,
        "l": "0.00000000",
        "z": "0.00000000",
        "L": "0.00000000",
        "n": "0",
        "N": null,
        "T": 1656027820174,
        "t": -1,
        "I": 23804088798,
        "w": true,
        "m": false,
        "M": false,
        "O": 1656027820174,
        "Z": "0.00000000",
        "Y": "0.00000000",
        "Q": "0.00000000"
    }
}

Code

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("i")]
    public long Id { get; init; }

    [JsonPropertyName("s")]
    public string Symbol { get; init; } = null!;

    [JsonPropertyName("c")]
    public string ClientOrderId { get; init; } = null!;

    [JsonPropertyName("S")]
    public Side Side { get; init; }

    [JsonPropertyName("o")]
    public SpotOrderType Type { get; init; }

    [JsonPropertyName("f")]
    public TimeInForce TimeInForce { get; init; }

    [JsonPropertyName("q")]
    public decimal Quantity { get; init; }

    [JsonPropertyName("p")]
    public decimal Price { get; init; }

    [JsonPropertyName("P")]
    public decimal StopPrice { get; init; }

    [JsonPropertyName("d")]
    public int? TrailingDelta { get; init; }

    [JsonPropertyName("F")]
    public decimal IcebergQuantity { get; init; }

    [JsonPropertyName("C")]
    public string? OriginalClientOrderId { get; init; }

    [JsonPropertyName("x")]
    public ExecutionType ExecutionType { get; init; }

    [JsonPropertyName("X")]
    public OrderStatus Status { get; init; }

    [JsonPropertyName("r")]
    public OrderRejectReason RejectReason { get; init; }

    [JsonPropertyName("l")]
    public decimal LastQuantityFilled { get; init; }

    [JsonPropertyName("z")]
    public decimal QuantityFilled { get; init; }

    [JsonPropertyName("L")]
    public decimal LastPriceFilled { get; init; }

    [JsonPropertyName("n")]
    public decimal Fee { get; init; }

    [JsonPropertyName("N")]
    public string FeeAsset { get; init; } = string.Empty;

    [JsonPropertyName("T")]
    [JsonConverter(typeof(MillisecondEpochDateTimeConverter))]
    public DateTime UpdateTime { get; init; }

    [JsonPropertyName("t")]
    public long TradeId { get; init; }

    [JsonPropertyName("w")]
    public bool IsWorking { get; init; }

    [JsonPropertyName("m")]
    public bool BuyerIsMaker { get; init; }

    [JsonPropertyName("O")]
    [JsonConverter(typeof(MillisecondEpochDateTimeConverter))]
    public DateTime CreateTime { get; init; }

    [JsonPropertyName("Z")]
    public decimal QuoteQuantityFilled { get; init; }

    [JsonPropertyName("Q")]
    public decimal QuoteQuantity { get; init; }

    [JsonPropertyName("Y")]
    public decimal LastQuoteQuantity { get; init; }

    [JsonPropertyName("g")]
    public long OrderListId { get; init; }

    [JsonPropertyName("I")]
    public long I { get; init; }

    [JsonPropertyName("M")]
    public bool M { get; init; }
}

public Task<SubscriptionToken> SubscribeToUserDataUpdatesAsync(string listenKey,
    Func<BinanceStreamOrderUpdate, ValueTask> onOrderUpdateMessage,
    Func<BinanceStreamOrderList, ValueTask> onOcoOrderUpdateMessage,
    Func<BinanceStreamPositionsUpdate, ValueTask> onAccountPositionMessage,
    Func<BinanceStreamBalanceUpdate, ValueTask> onAccountBalanceUpdate)
{
    var handler = new Func<string, ValueTask>(data =>
    {
        using var document = JsonDocument.Parse(data);

        if (!document.RootElement.TryGetProperty("data", out var dataElement))
        {
            return ValueTask.CompletedTask;
        }

        if (dataElement.TryGetProperty("e", out var eElement))
        {
            var e = eElement.GetString();

            switch (e)
            {
                case "outboundAccountPosition":
                    break;
                case "balanceUpdate":
                    break;
                case "executionReport":
                    var result = dataElement.Deserialize<BinanceStreamOrderUpdate>();
                    return onOrderUpdateMessage(result!);
                case "listStatus":
                    break;
            }
        }

        return ValueTask.CompletedTask;
    });

    return _client.SubscribeAsync(listenKey, handler);
}
tymtam
  • 31,798
  • 8
  • 86
  • 126
nop
  • 4,711
  • 6
  • 32
  • 93
  • Also why not just use an enum with the [`JsonStringEnumConverter`](https://github.com/Macross-Software/core/blob/5a370afc9f7432ea9e504a825ef5a3923d9a6800/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverter.cs) from the [`Macross.Json.Extensions`](https://www.nuget.org/packages/Macross.Json.Extensions/) NuGet package and the and [`EnumMemberAttribute`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.enummemberattribute?view=net-6.0)? – Jesse Jun 24 '22 at 00:01
  • 1
    @Jesse, I prefer this way rather than installing additional dependencies – nop Jun 24 '22 at 00:01
  • @Jesse, this one is Json.NET related. I'm using System.Text.Json – nop Jun 24 '22 at 00:02
  • The concept is still the same. They both have `JsonConverter`s that are defined in basically the same way. I think the answer still applies. The only difference I believe is System.Text.Json doesn't have the `CanConvert` method. – Jesse Jun 24 '22 at 00:04
  • Accidentally deleted the comment so [here](https://stackoverflow.com/a/24726372/10601203)'s the answer again. – Jesse Jun 24 '22 at 00:06
  • What does having a struct give you? – tymtam Jun 24 '22 at 03:54
  • 2
    Could it be because `Side`'s constructor is private? If you remove `Side` from `BinanceStreamOrderUpdate` and the JSON, does it work? – Andrew Jun 24 '22 at 04:12
  • @Andrew, I made the constructor `public` but the issue still remains. If I remove `Side` and the rest of the structs, it works just fine. – nop Jun 24 '22 at 07:20

1 Answers1

1

Side, SpotOrderType, etc. really look like enums. You can use the native JsonStringEnumConverter to do the work:

public enum Side {
    Buy = 0,
    Sell  = 1
};

public record BinanceStreamOrderUpdate
{
    [JsonConverter(typeof(JsonStringEnumConverter))] 
    [JsonPropertyName("S")]
    public Side? Side { get; init; }
};


    
public static async Task Main()
{
    var json = "{ \"S\": \"BUY\"}";

    var o = JsonSerializer.Deserialize<BinanceStreamOrderUpdate>(json);

    Console.WriteLine(o);
}

This prints:

BinanceStreamOrderUpdate { Side = Buy }
tymtam
  • 31,798
  • 8
  • 86
  • 126