Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed JSON deserialization in Coinbase with new SerializerSettings #757

Merged
merged 1 commit into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/ExchangeSharp/API/Common/BaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The above copyright notice and this permission notice shall be included in all c

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

namespace ExchangeSharp
{
Expand Down Expand Up @@ -246,6 +247,16 @@ public RateGate RateLimit
/// </summary>
public Dictionary<string, TimeSpan> MethodCachePolicy { get; } = new Dictionary<string, TimeSpan>();

public static JsonSerializerSettings SerializerSettings { get; } = new JsonSerializerSettings
{
FloatParseHandling = FloatParseHandling.Decimal,
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
},
};

private ICache cache = new MemoryCache();
/// <summary>
/// Get or set the current cache. Defaults to MemoryCache.
Expand Down Expand Up @@ -503,7 +514,7 @@ public async Task<T> MakeJsonRequestAsync<T>(string url, string? baseUrl = null,
await new SynchronizationContextRemover();

string stringResult = await MakeRequestAsync(url, baseUrl: baseUrl, payload: payload, method: requestMethod);
T jsonResult = JsonConvert.DeserializeObject<T>(stringResult);
T jsonResult = JsonConvert.DeserializeObject<T>(stringResult, SerializerSettings);
if (jsonResult is JToken token)
{
return (T)(object)CheckJsonResponse(token);
Expand Down
24 changes: 12 additions & 12 deletions src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ protected override Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(Action<Exc
if (message.Contains(@"""l2update"""))
{
// parse delta update
var delta = JsonConvert.DeserializeObject<Level2>(message);
var delta = JsonConvert.DeserializeObject<Level2>(message, SerializerSettings);
book.MarketSymbol = delta.ProductId;
book.SequenceId = delta.Time.Ticks;
foreach (string[] change in delta.Changes)
Expand All @@ -336,7 +336,7 @@ protected override Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(Action<Exc
else if (message.Contains(@"""snapshot"""))
{
// parse snapshot
var snapshot = JsonConvert.DeserializeObject<Snapshot>(message);
var snapshot = JsonConvert.DeserializeObject<Snapshot>(message, SerializerSettings);
book.MarketSymbol = snapshot.ProductId;
foreach (decimal[] ask in snapshot.Asks)
{
Expand Down Expand Up @@ -451,11 +451,11 @@ protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object
return await ConnectPublicWebSocketAsync("/", async (_socket, msg) =>
{
var token = msg.ToStringFromUTF8();
var response = JsonConvert.DeserializeObject<BaseMessage>(token);
var response = JsonConvert.DeserializeObject<BaseMessage>(token, SerializerSettings);
switch (response.Type)
{
case ResponseType.Subscriptions:
var subscription = JsonConvert.DeserializeObject<Subscription>(token);
var subscription = JsonConvert.DeserializeObject<Subscription>(token, SerializerSettings);
if (subscription.Channels == null || !subscription.Channels.Any())
{
Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() no channels subscribed");
Expand All @@ -473,37 +473,37 @@ protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object
case ResponseType.L2Update:
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
case ResponseType.Heartbeat:
var heartbeat = JsonConvert.DeserializeObject<Heartbeat>(token);
var heartbeat = JsonConvert.DeserializeObject<Heartbeat>(token, SerializerSettings);
Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() heartbeat received {heartbeat}");
break;
case ResponseType.Received:
var received = JsonConvert.DeserializeObject<Received>(token);
var received = JsonConvert.DeserializeObject<Received>(token, SerializerSettings);
callback(received.ExchangeOrderResult);
break;
case ResponseType.Open:
var open = JsonConvert.DeserializeObject<Open>(token);
var open = JsonConvert.DeserializeObject<Open>(token, SerializerSettings);
callback(open.ExchangeOrderResult);
break;
case ResponseType.Done:
var done = JsonConvert.DeserializeObject<Done>(token);
var done = JsonConvert.DeserializeObject<Done>(token, SerializerSettings);
callback(done.ExchangeOrderResult);
break;
case ResponseType.Match:
var match = JsonConvert.DeserializeObject<Match>(token);
var match = JsonConvert.DeserializeObject<Match>(token, SerializerSettings);
callback(match.ExchangeOrderResult);
break;
case ResponseType.LastMatch:
//var lastMatch = JsonConvert.DeserializeObject<LastMatch>(token);
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
case ResponseType.Error:
var error = JsonConvert.DeserializeObject<Error>(token);
var error = JsonConvert.DeserializeObject<Error>(token, SerializerSettings);
throw new APIException($"{error.Reason}: {error.Message}");
case ResponseType.Change:
var change = JsonConvert.DeserializeObject<Change>(token);
var change = JsonConvert.DeserializeObject<Change>(token, SerializerSettings);
callback(change.ExchangeOrderResult);
break;
case ResponseType.Activate:
var activate = JsonConvert.DeserializeObject<Activate>(token);
var activate = JsonConvert.DeserializeObject<Activate>(token, SerializerSettings);
callback(activate.ExchangeOrderResult);
break;
case ResponseType.Status:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class Activate : BaseMessage
OrderId = OrderId.ToString(),
ClientOrderId = null, // not provided here
Result = ExchangeAPIOrderResult.PendingOpen, // order has just been activated (so it starts in PendingOpen)
Message = null, // can use for something in the future if needed
Message = null, // + can use for something in the future if needed
Amount = Size,
AmountFilled = 0, // just activated, so none filled
Price = null, // not specified here (only StopPrice is)
Expand Down Expand Up @@ -78,7 +78,7 @@ internal class Change : BaseMessage
AmountFilled = null, // not specified here
Price = Price,
AveragePrice = null, // not specified here
// OrderDate - unclear if the Time in the Change msg is the new OrderDate or whether that is unchanged
OrderDate = Time, // + unclear if the Time in the Change msg is the new OrderDate or whether that is unchanged
CompletedDate = null, // order is active
MarketSymbol = ProductId,
IsBuy = Side == OrderSide.Buy,
Expand Down Expand Up @@ -132,7 +132,7 @@ internal class Heartbeat : BaseMessage
public long LastTradeId { get; set; }
public string ProductId { get; set; }
public long Sequence { get; set; }
public System.DateTimeOffset Time { get; set; }
public DateTimeOffset Time { get; set; }
public override string ToString()
{
return $"Heartbeat: Last TID {LastTradeId}, Product Id {ProductId}, Sequence {Sequence}, Time {Time}";
Expand Down Expand Up @@ -185,7 +185,7 @@ internal class Match : BaseMessage
AveragePrice = Price, // not specified here
// OrderDate - not provided here. ideally would be null but ExchangeOrderResult.OrderDate is not nullable
CompletedDate = null, // order not necessarily fullly filled at this point
TradeDate = Time.ToDateTimeInvariant(),
TradeDate = Time.UtcDateTime,
MarketSymbol = ProductId,
IsBuy = Side == OrderSide.Buy,
Fees = (MakerFeeRate ?? TakerFeeRate) * Price * Size,
Expand Down
1 change: 0 additions & 1 deletion src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ The above copyright notice and this permission notice shall be included in all c
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

Expand Down
11 changes: 6 additions & 5 deletions tests/ExchangeSharpTests/ExchangeBinanceAPITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void DeserializeDiff()
]
]
}";
var diff = JsonConvert.DeserializeObject<MarketDepthDiffUpdate>(toParse);
var diff = JsonConvert.DeserializeObject<MarketDepthDiffUpdate>(toParse, BaseAPI.SerializerSettings);
ValidateDiff(diff);
}

Expand Down Expand Up @@ -82,7 +82,7 @@ public void DeserializeComboStream()
}
}";

var multistream = JsonConvert.DeserializeObject<MultiDepthStream>(toParse);
var multistream = JsonConvert.DeserializeObject<MultiDepthStream>(toParse, BaseAPI.SerializerSettings);
multistream.Stream.Should().Be("bnbbtc@depth");
ValidateDiff(multistream.Data);
}
Expand All @@ -92,16 +92,17 @@ public void DeserializeRealData()
{
string real =
"{\"stream\":\"bnbbtc@depth\",\"data\":{\"e\":\"depthUpdate\",\"E\":1527540113575,\"s\":\"BNBBTC\",\"U\":77730662,\"u\":77730663,\"b\":[[\"0.00167300\",\"0.00000000\",[]],[\"0.00165310\",\"16.44000000\",[]]],\"a\":[]}}";
var diff = JsonConvert.DeserializeObject<MultiDepthStream>(real);
var diff = JsonConvert.DeserializeObject<MultiDepthStream>(real, BaseAPI.SerializerSettings);
diff.Data.EventTime.Should().Be(1527540113575);
}

[TestMethod]
public async Task CurrenciesParsedCorrectly()
{
var requestMaker = Substitute.For<IAPIRequestMaker>();
requestMaker.MakeRequestAsync("/capital/config/getall", new ExchangeBinanceAPI().BaseUrlSApi).Returns(Resources.BinanceGetAllAssets);
var binance = new ExchangeBinanceAPI { RequestMaker = requestMaker };
var binance = await ExchangeAPI.GetExchangeAPIAsync<ExchangeBinanceAPI>();
binance.RequestMaker = requestMaker;
requestMaker.MakeRequestAsync("/capital/config/getall", ((ExchangeBinanceAPI)binance).BaseUrlSApi).Returns(Resources.BinanceGetAllAssets);
IReadOnlyDictionary<string, ExchangeCurrency> currencies = await binance.GetCurrenciesAsync();
currencies.Should().HaveCount(3);
currencies.TryGetValue("bnb", out ExchangeCurrency bnb).Should().BeTrue();
Expand Down
39 changes: 39 additions & 0 deletions tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using ExchangeSharp;
using ExchangeSharp.Coinbase;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExchangeSharpTests
{
[TestClass]
public sealed class ExchangeCoinbaseAPITests
{
private async Task<ExchangeCoinbaseAPI> MakeMockRequestMaker(string response = null)
{
var requestMaker = new MockAPIRequestMaker();
if (response != null)
{
requestMaker.GlobalResponse = response;
}
var api = (await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Coinbase) as ExchangeCoinbaseAPI)!;
api.RequestMaker = requestMaker;
return api;
}

[TestMethod]
public void DeserializeUserMatch()
{
string toParse = "{\r\n \"type\": \"match\",\r\n \"trade_id\": 10,\r\n \"sequence\": 50,\r\n \"maker_order_id\": \"ac928c66-ca53-498f-9c13-a110027a60e8\",\r\n \"taker_order_id\": \"132fb6ae-456b-4654-b4e0-d681ac05cea1\",\r\n \"time\": \"2014-11-07T08:19:27.028459Z\",\r\n \"product_id\": \"BTC-USD\",\r\n \"size\": \"5.23512\",\r\n \"price\": \"400.23\",\r\n \"side\": \"sell\"\r\n,\r\n \"taker_user_id\": \"5844eceecf7e803e259d0365\",\r\n \"user_id\": \"5844eceecf7e803e259d0365\",\r\n \"taker_profile_id\": \"765d1549-9660-4be2-97d4-fa2d65fa3352\",\r\n \"profile_id\": \"765d1549-9660-4be2-97d4-fa2d65fa3352\",\r\n \"taker_fee_rate\": \"0.005\"\r\n}";

var usermatch = JsonConvert.DeserializeObject<Match>(toParse, BaseAPI.SerializerSettings);
usermatch.MakerOrderId.Should().Be("ac928c66-ca53-498f-9c13-a110027a60e8");
usermatch.ExchangeOrderResult.OrderId.Should().Be("132fb6ae-456b-4654-b4e0-d681ac05cea1");
}
}
}
2 changes: 1 addition & 1 deletion tests/ExchangeSharpTests/ExchangeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task GlobalSymbolTest()

string globalMarketSymbol = "ETH-BTC"; //1 ETH is worth 0.0192 BTC...
string globalMarketSymbolAlt = "BTC-KRW"; // WTF Bitthumb... //1 BTC worth 9,783,000 won
Dictionary<string, string[]> allSymbols = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(System.IO.File.ReadAllText("TestData/AllSymbols.json"));
Dictionary<string, string[]> allSymbols = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(System.IO.File.ReadAllText("TestData/AllSymbols.json"), ExchangeAPI.SerializerSettings);

// sanity test that all exchanges return the same global symbol when converted back and forth
foreach (IExchangeAPI api in await ExchangeAPI.GetExchangeAPIsAsync())
Expand Down