diff --git a/CryptoExchange.Net.UnitTests/JsonNetConverterTests.cs b/CryptoExchange.Net.UnitTests/JsonNetConverterTests.cs deleted file mode 100644 index c23651b..0000000 --- a/CryptoExchange.Net.UnitTests/JsonNetConverterTests.cs +++ /dev/null @@ -1,249 +0,0 @@ -using CryptoExchange.Net.Attributes; -using CryptoExchange.Net.Converters; -using CryptoExchange.Net.Converters.JsonNet; -using Newtonsoft.Json; -using NUnit.Framework; -using NUnit.Framework.Legacy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CryptoExchange.Net.UnitTests -{ - [TestFixture()] - public class JsonNetConverterTests - { - [TestCase("2021-05-12")] - [TestCase("20210512")] - [TestCase("210512")] - [TestCase("1620777600.000")] - [TestCase("1620777600000")] - [TestCase("2021-05-12T00:00:00.000Z")] - [TestCase("2021-05-12T00:00:00.000000000Z")] - [TestCase("0.000000", true)] - [TestCase("0", true)] - [TestCase("", true)] - [TestCase(" ", true)] - public void TestDateTimeConverterString(string input, bool expectNull = false) - { - var output = JsonConvert.DeserializeObject($"{{ \"time\": \"{input}\" }}"); - Assert.That(output.Time == (expectNull ? null: new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); - } - - [TestCase(1620777600.000)] - [TestCase(1620777600000d)] - public void TestDateTimeConverterDouble(double input) - { - var output = JsonConvert.DeserializeObject($"{{ \"time\": {input} }}"); - Assert.That(output.Time == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - } - - [TestCase(1620777600)] - [TestCase(1620777600000)] - [TestCase(1620777600000000)] - [TestCase(1620777600000000000)] - [TestCase(0, true)] - public void TestDateTimeConverterLong(long input, bool expectNull = false) - { - var output = JsonConvert.DeserializeObject($"{{ \"time\": {input} }}"); - Assert.That(output.Time == (expectNull ? null : new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc))); - } - - [TestCase(1620777600)] - [TestCase(1620777600.000)] - public void TestDateTimeConverterFromSeconds(double input) - { - var output = DateTimeConverter.ConvertFromSeconds(input); - Assert.That(output == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - } - - [Test] - public void TestDateTimeConverterToSeconds() - { - var output = DateTimeConverter.ConvertToSeconds(new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - Assert.That(output == 1620777600); - } - - [TestCase(1620777600000)] - [TestCase(1620777600000.000)] - public void TestDateTimeConverterFromMilliseconds(double input) - { - var output = DateTimeConverter.ConvertFromMilliseconds(input); - Assert.That(output == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - } - - [Test] - public void TestDateTimeConverterToMilliseconds() - { - var output = DateTimeConverter.ConvertToMilliseconds(new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - Assert.That(output == 1620777600000); - } - - [TestCase(1620777600000000)] - public void TestDateTimeConverterFromMicroseconds(long input) - { - var output = DateTimeConverter.ConvertFromMicroseconds(input); - Assert.That(output == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - } - - [Test] - public void TestDateTimeConverterToMicroseconds() - { - var output = DateTimeConverter.ConvertToMicroseconds(new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - Assert.That(output == 1620777600000000); - } - - [TestCase(1620777600000000000)] - public void TestDateTimeConverterFromNanoseconds(long input) - { - var output = DateTimeConverter.ConvertFromNanoseconds(input); - Assert.That(output == new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - } - - [Test] - public void TestDateTimeConverterToNanoseconds() - { - var output = DateTimeConverter.ConvertToNanoseconds(new DateTime(2021, 05, 12, 0, 0, 0, DateTimeKind.Utc)); - Assert.That(output == 1620777600000000000); - } - - [TestCase()] - public void TestDateTimeConverterNull() - { - var output = JsonConvert.DeserializeObject($"{{ \"time\": null }}"); - Assert.That(output.Time == null); - } - - [TestCase(TestEnum.One, "1")] - [TestCase(TestEnum.Two, "2")] - [TestCase(TestEnum.Three, "three")] - [TestCase(TestEnum.Four, "Four")] - [TestCase(null, null)] - public void TestEnumConverterNullableGetStringTests(TestEnum? value, string expected) - { - var output = EnumConverter.GetString(value); - Assert.That(output == expected); - } - - [TestCase(TestEnum.One, "1")] - [TestCase(TestEnum.Two, "2")] - [TestCase(TestEnum.Three, "three")] - [TestCase(TestEnum.Four, "Four")] - public void TestEnumConverterGetStringTests(TestEnum value, string expected) - { - var output = EnumConverter.GetString(value); - Assert.That(output == expected); - } - - [TestCase("1", TestEnum.One)] - [TestCase("2", TestEnum.Two)] - [TestCase("3", TestEnum.Three)] - [TestCase("three", TestEnum.Three)] - [TestCase("Four", TestEnum.Four)] - [TestCase("four", TestEnum.Four)] - [TestCase("Four1", null)] - [TestCase(null, null)] - public void TestEnumConverterNullableDeserializeTests(string value, TestEnum? expected) - { - var val = value == null ? "null" : $"\"{value}\""; - var output = JsonConvert.DeserializeObject($"{{ \"Value\": {val} }}"); - Assert.That(output.Value == expected); - } - - [TestCase("1", TestEnum.One)] - [TestCase("2", TestEnum.Two)] - [TestCase("3", TestEnum.Three)] - [TestCase("three", TestEnum.Three)] - [TestCase("Four", TestEnum.Four)] - [TestCase("four", TestEnum.Four)] - [TestCase("Four1", TestEnum.One)] - [TestCase(null, TestEnum.One)] - public void TestEnumConverterNotNullableDeserializeTests(string value, TestEnum? expected) - { - var val = value == null ? "null" : $"\"{value}\""; - var output = JsonConvert.DeserializeObject($"{{ \"Value\": {val} }}"); - Assert.That(output.Value == expected); - } - - [TestCase("1", true)] - [TestCase("true", true)] - [TestCase("yes", true)] - [TestCase("y", true)] - [TestCase("on", true)] - [TestCase("-1", false)] - [TestCase("0", false)] - [TestCase("n", false)] - [TestCase("no", false)] - [TestCase("false", false)] - [TestCase("off", false)] - [TestCase("", null)] - public void TestBoolConverter(string value, bool? expected) - { - var val = value == null ? "null" : $"\"{value}\""; - var output = JsonConvert.DeserializeObject($"{{ \"Value\": {val} }}"); - Assert.That(output.Value == expected); - } - - [TestCase("1", true)] - [TestCase("true", true)] - [TestCase("yes", true)] - [TestCase("y", true)] - [TestCase("on", true)] - [TestCase("-1", false)] - [TestCase("0", false)] - [TestCase("n", false)] - [TestCase("no", false)] - [TestCase("false", false)] - [TestCase("off", false)] - [TestCase("", false)] - public void TestBoolConverterNotNullable(string value, bool expected) - { - var val = value == null ? "null" : $"\"{value}\""; - var output = JsonConvert.DeserializeObject($"{{ \"Value\": {val} }}"); - Assert.That(output.Value == expected); - } - } - - public class TimeObject - { - [JsonConverter(typeof(DateTimeConverter))] - public DateTime? Time { get; set; } - } - - public class EnumObject - { - public TestEnum? Value { get; set; } - } - - public class NotNullableEnumObject - { - public TestEnum Value { get; set; } - } - - public class BoolObject - { - [JsonConverter(typeof(BoolConverter))] - public bool? Value { get; set; } - } - - public class NotNullableBoolObject - { - [JsonConverter(typeof(BoolConverter))] - public bool Value { get; set; } - } - - [JsonConverter(typeof(EnumConverter))] - [System.Text.Json.Serialization.JsonConverter(typeof(CryptoExchange.Net.Converters.SystemTextJson.EnumConverter))] - public enum TestEnum - { - [Map("1")] - One, - [Map("2")] - Two, - [Map("three", "3")] - Three, - Four - } -} diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index 6f1abfa..6e4d1bb 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -1,14 +1,9 @@ -using CryptoExchange.Net.Authentication; -using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects; using CryptoExchange.Net.UnitTests.TestImplementations; -using Newtonsoft.Json; using NUnit.Framework; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using CryptoExchange.Net.Interfaces; -using Microsoft.Extensions.Logging; using System.Net.Http; using System.Threading.Tasks; using System.Threading; @@ -18,6 +13,7 @@ using System.Net; using CryptoExchange.Net.RateLimiting.Guards; using CryptoExchange.Net.RateLimiting.Filters; using CryptoExchange.Net.RateLimiting.Interfaces; +using System.Text.Json; namespace CryptoExchange.Net.UnitTests { @@ -30,7 +26,7 @@ namespace CryptoExchange.Net.UnitTests // arrange var client = new TestRestClient(); var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" }; - client.SetResponse(JsonConvert.SerializeObject(expected), out _); + client.SetResponse(JsonSerializer.Serialize(expected, new JsonSerializerOptions { TypeInfoResolver = new TestSerializerContext() }), out _); // act var result = client.Api1.Request().Result; @@ -140,7 +136,7 @@ namespace CryptoExchange.Net.UnitTests client.SetResponse("{}", out var request); - await client.Api1.RequestWithParams(new HttpMethod(method), new Dictionary + await client.Api1.RequestWithParams(new HttpMethod(method), new ParameterCollection { { "TestParam1", "Value1" }, { "TestParam2", 2 }, diff --git a/CryptoExchange.Net.UnitTests/SocketClientTests.cs b/CryptoExchange.Net.UnitTests/SocketClientTests.cs index 51c157a..7f85384 100644 --- a/CryptoExchange.Net.UnitTests/SocketClientTests.cs +++ b/CryptoExchange.Net.UnitTests/SocketClientTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Objects; @@ -9,7 +10,6 @@ using CryptoExchange.Net.UnitTests.TestImplementations; using CryptoExchange.Net.UnitTests.TestImplementations.Sockets; using Microsoft.Extensions.Logging; using Moq; -using Newtonsoft.Json; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -103,7 +103,7 @@ namespace CryptoExchange.Net.UnitTests rstEvent.Set(); }); sub.AddSubscription(subObj); - var msgToSend = JsonConvert.SerializeObject(new { topic = "topic", action = "update", property = 123 }); + var msgToSend = JsonSerializer.Serialize(new { topic = "topic", action = "update", property = "123" }); // act socket.InvokeMessage(msgToSend); @@ -198,7 +198,7 @@ namespace CryptoExchange.Net.UnitTests // act var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => {}, ct: default); - socket.InvokeMessage(JsonConvert.SerializeObject(new { channel, action = "subscribe", status = "error" })); + socket.InvokeMessage(JsonSerializer.Serialize(new { channel, action = "subscribe", status = "error" })); await sub; // assert @@ -221,7 +221,7 @@ namespace CryptoExchange.Net.UnitTests // act var sub = client.SubClient.SubscribeToSomethingAsync(channel, onUpdate => {}, ct: default); - socket.InvokeMessage(JsonConvert.SerializeObject(new { channel, action = "subscribe", status = "confirmed" })); + socket.InvokeMessage(JsonSerializer.Serialize(new { channel, action = "subscribe", status = "confirmed" })); await sub; // assert diff --git a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs index d3db64a..2ec776b 100644 --- a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs @@ -363,6 +363,18 @@ namespace CryptoExchange.Net.UnitTests public string Prop32 { get; set; } } + [JsonConverter(typeof(EnumConverter))] + public enum TestEnum + { + [Map("1")] + One, + [Map("2")] + Two, + [Map("three", "3")] + Three, + Four + } + [JsonSerializable(typeof(Test))] [JsonSerializable(typeof(Test2))] [JsonSerializable(typeof(Test3))] diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs index 12ff600..0af9b97 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/Sockets/TestChannelQuery.cs @@ -1,32 +1,31 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; -using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Threading.Tasks; +using System.Text.Json.Serialization; namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets { internal class SubResponse { - [JsonProperty("action")] + [JsonPropertyName("action")] public string Action { get; set; } = null!; - [JsonProperty("channel")] + [JsonPropertyName("channel")] public string Channel { get; set; } = null!; - [JsonProperty("status")] + [JsonPropertyName("status")] public string Status { get; set; } = null!; } internal class UnsubResponse { - [JsonProperty("action")] + [JsonPropertyName("action")] public string Action { get; set; } = null!; - [JsonProperty("status")] + [JsonPropertyName("status")] public string Status { get; set; } = null!; } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs index 57abfe8..e21317a 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs @@ -3,9 +3,12 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Clients; +using CryptoExchange.Net.Converters.SystemTextJson; +using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.SharedApis; @@ -59,6 +62,8 @@ namespace CryptoExchange.Net.UnitTests public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}"; public override TimeSpan? GetTimeOffset() => null; public override TimeSyncInfo GetTimeSyncInfo() => null; + protected override IStreamMessageAccessor CreateAccessor() => new SystemTextJsonStreamMessageAccessor(new System.Text.Json.JsonSerializerOptions()); + protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new TestSerializerContext()); protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => throw new NotImplementedException(); protected override Task> GetServerTimestampAsync() => throw new NotImplementedException(); } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestObjects.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestObjects.cs index 0ad099d..7827d0c 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestObjects.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestObjects.cs @@ -1,12 +1,14 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CryptoExchange.Net.UnitTests.TestImplementations { public class TestObject { - [JsonProperty("other")] + [JsonPropertyName("other")] public string StringData { get; set; } + [JsonPropertyName("intData")] public int IntData { get; set; } + [JsonPropertyName("decimalData")] public decimal DecimalData { get; set; } } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs index f1841d3..d4f5da1 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs @@ -1,7 +1,6 @@ using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using Moq; -using Newtonsoft.Json.Linq; using System; using System.IO; using System.Net; @@ -12,12 +11,13 @@ using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; using System.Collections.Generic; -using CryptoExchange.Net.Objects.Options; using Microsoft.Extensions.Logging; using CryptoExchange.Net.Clients; using CryptoExchange.Net.SharedApis; using Microsoft.Extensions.Options; using System.Linq; +using CryptoExchange.Net.Converters.SystemTextJson; +using System.Text.Json.Serialization; namespace CryptoExchange.Net.UnitTests.TestImplementations { @@ -138,14 +138,17 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}"; + protected override IStreamMessageAccessor CreateAccessor() => new SystemTextJsonStreamMessageAccessor(new System.Text.Json.JsonSerializerOptions() { TypeInfoResolver = new TestSerializerContext() }); + protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new TestSerializerContext()); + public async Task> Request(CancellationToken ct = default) where T : class { - return await SendRequestAsync(new Uri("http://www.test.com"), HttpMethod.Get, ct, requestWeight: 0); + return await SendAsync("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct); } - public async Task> RequestWithParams(HttpMethod method, Dictionary parameters, Dictionary headers) where T : class + public async Task> RequestWithParams(HttpMethod method, ParameterCollection parameters, Dictionary headers) where T : class { - return await SendRequestAsync(new Uri("http://www.test.com"), method, default, parameters, requestWeight: 0, additionalHeaders: headers); + return await SendAsync("http://www.test.com", new RequestDefinition("/", method) { Weight = 0 }, parameters, default, additionalHeaders: headers); } public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position) @@ -179,12 +182,15 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations RequestFactory = new Mock().Object; } + protected override IStreamMessageAccessor CreateAccessor() => new SystemTextJsonStreamMessageAccessor(new System.Text.Json.JsonSerializerOptions()); + protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new TestSerializerContext()); + /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}"; public async Task> Request(CancellationToken ct = default) where T : class { - return await SendRequestAsync(new Uri("http://www.test.com"), HttpMethod.Get, ct, requestWeight: 0); + return await SendAsync("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct); } protected override Error ParseErrorResponse(int httpStatusCode, KeyValuePair[] responseHeaders, IMessageAccessor accessor) @@ -215,7 +221,9 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations public class TestError { + [JsonPropertyName("errorCode")] public int ErrorCode { get; set; } + [JsonPropertyName("errorMessage")] public string ErrorMessage { get; set; } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs index d74060e..55f51e2 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestSocketClient.cs @@ -16,6 +16,7 @@ using Moq; using CryptoExchange.Net.Testing.Implementations; using CryptoExchange.Net.SharedApis; using Microsoft.Extensions.Options; +using CryptoExchange.Net.Converters.SystemTextJson; namespace CryptoExchange.Net.UnitTests.TestImplementations { @@ -97,6 +98,9 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations } + protected internal override IByteMessageAccessor CreateAccessor() => new SystemTextJsonByteMessageAccessor(new System.Text.Json.JsonSerializerOptions()); + protected internal override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new TestSerializerContext()); + /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}"; diff --git a/CryptoExchange.Net.UnitTests/TestSerializerContext.cs b/CryptoExchange.Net.UnitTests/TestSerializerContext.cs new file mode 100644 index 0000000..e3dfe5b --- /dev/null +++ b/CryptoExchange.Net.UnitTests/TestSerializerContext.cs @@ -0,0 +1,21 @@ +using CryptoExchange.Net.UnitTests.TestImplementations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.UnitTests +{ + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(IDictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(IDictionary))] + [JsonSerializable(typeof(TestObject))] + internal partial class TestSerializerContext : JsonSerializerContext + { + } +} diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 4d4504a..5d61325 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; @@ -9,7 +8,6 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Caching; -using CryptoExchange.Net.Converters.JsonNet; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Objects; @@ -114,13 +112,13 @@ namespace CryptoExchange.Net.Clients /// Create a message accessor instance /// /// - protected virtual IStreamMessageAccessor CreateAccessor() => new JsonNetStreamMessageAccessor(); + protected abstract IStreamMessageAccessor CreateAccessor(); /// /// Create a serializer instance /// /// - protected virtual IMessageSerializer CreateSerializer() => new JsonNetMessageSerializer(); + protected abstract IMessageSerializer CreateSerializer(); /// /// Send a request to the base address based on the request definition diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs index 5098b7b..af15b67 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -1,4 +1,3 @@ -using CryptoExchange.Net.Converters.JsonNet; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Objects; @@ -133,13 +132,13 @@ namespace CryptoExchange.Net.Clients /// Create a message accessor instance /// /// - protected internal virtual IByteMessageAccessor CreateAccessor() => new JsonNetByteMessageAccessor(); + protected internal abstract IByteMessageAccessor CreateAccessor(); /// /// Create a serializer instance /// /// - protected internal virtual IMessageSerializer CreateSerializer() => new JsonNetMessageSerializer(); + protected internal abstract IMessageSerializer CreateSerializer(); /// /// Keep an open connection to this url diff --git a/CryptoExchange.Net/Converters/JsonNet/ArrayConverter.cs b/CryptoExchange.Net/Converters/JsonNet/ArrayConverter.cs deleted file mode 100644 index 4a2180e..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/ArrayConverter.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Globalization; -using System.Linq; -using System.Reflection; -using CryptoExchange.Net.Attributes; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Converter for arrays to objects. Can deserialize data like [0.1, 0.2, "test"] to an object. Mapping is done by marking the class with [JsonConverter(typeof(ArrayConverter))] and the properties - /// with [ArrayProperty(x)] where x is the index of the property in the array - /// - public class ArrayConverter : JsonConverter - { - private static readonly ConcurrentDictionary<(MemberInfo, Type), Attribute> _attributeByMemberInfoAndTypeCache = new ConcurrentDictionary<(MemberInfo, Type), Attribute>(); - private static readonly ConcurrentDictionary<(Type, Type), Attribute> _attributeByTypeAndTypeCache = new ConcurrentDictionary<(Type, Type), Attribute>(); - - /// - public override bool CanConvert(Type objectType) - { - return true; - } - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return null; - - if (objectType == typeof(JToken)) - return JToken.Load(reader); - - var result = Activator.CreateInstance(objectType); - var arr = JArray.Load(reader); - return ParseObject(arr, result!, objectType); - } - - private static object ParseObject(JArray arr, object result, Type objectType) - { - foreach (var property in objectType.GetProperties()) - { - var attribute = GetCustomAttribute(property); - - if (attribute == null) - continue; - - if (attribute.Index >= arr.Count) - continue; - - if (property.PropertyType.BaseType == typeof(Array)) - { - var objType = property.PropertyType.GetElementType(); - var innerArray = (JArray)arr[attribute.Index]; - var count = 0; - if (innerArray.Count == 0) - { - var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 0 })!; - property.SetValue(result, arrayResult); - } - else if (innerArray[0].Type == JTokenType.Array) - { - var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { innerArray.Count })!; - foreach (var obj in innerArray) - { - var innerObj = Activator.CreateInstance(objType!); - arrayResult[count] = ParseObject((JArray)obj, innerObj!, objType!); - count++; - } - property.SetValue(result, arrayResult); - } - else - { - var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 1 })!; - var innerObj = Activator.CreateInstance(objType!); - arrayResult[0] = ParseObject(innerArray, innerObj!, objType!); - property.SetValue(result, arrayResult); - } - continue; - } - - var converterAttribute = GetCustomAttribute(property) ?? GetCustomAttribute(property.PropertyType); - var conversionAttribute = GetCustomAttribute(property) ?? GetCustomAttribute(property.PropertyType); - - object? value; - if (converterAttribute != null) - { - value = arr[attribute.Index].ToObject(property.PropertyType, new JsonSerializer {Converters = {(JsonConverter) Activator.CreateInstance(converterAttribute.ConverterType)!}}); - } - else if (conversionAttribute != null) - { - value = arr[attribute.Index].ToObject(property.PropertyType); - } - else - { - value = arr[attribute.Index]; - } - - if (value != null && property.PropertyType.IsInstanceOfType(value)) - { - property.SetValue(result, value); - } - else - { - if (value is JToken token) - { - if (token.Type == JTokenType.Null) - value = null; - - if (token.Type == JTokenType.Float) - value = token.Value(); - } - - if (value is decimal) - { - property.SetValue(result, value); - } - else if ((property.PropertyType == typeof(decimal) - || property.PropertyType == typeof(decimal?)) - && (value != null && value.ToString()!.IndexOf("e", StringComparison.OrdinalIgnoreCase) >= 0)) - { - var v = value.ToString(); - if (decimal.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec)) - property.SetValue(result, dec); - } - else - { - property.SetValue(result, value == null ? null : Convert.ChangeType(value, property.PropertyType)); - } - } - } - return result; - } - - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value == null) - return; - - writer.WriteStartArray(); - var props = value.GetType().GetProperties(); - var ordered = props.OrderBy(p => GetCustomAttribute(p)?.Index); - - var last = -1; - foreach (var prop in ordered) - { - var arrayProp = GetCustomAttribute(prop); - if (arrayProp == null) - continue; - - if (arrayProp.Index == last) - continue; - - while (arrayProp.Index != last + 1) - { - writer.WriteValue((string?)null); - last += 1; - } - - last = arrayProp.Index; - var converterAttribute = GetCustomAttribute(prop); - if (converterAttribute != null) - writer.WriteRawValue(JsonConvert.SerializeObject(prop.GetValue(value), (JsonConverter)Activator.CreateInstance(converterAttribute.ConverterType)!)); - else if (!IsSimple(prop.PropertyType)) - serializer.Serialize(writer, prop.GetValue(value)); - else - writer.WriteValue(prop.GetValue(value)); - } - writer.WriteEndArray(); - } - - private static bool IsSimple(Type type) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - // nullable type, check if the nested type is simple. - return IsSimple(type.GetGenericArguments()[0]); - } - return type.IsPrimitive - || type.IsEnum - || type == typeof(string) - || type == typeof(decimal); - } - - private static T? GetCustomAttribute(MemberInfo memberInfo) where T : Attribute => - (T?)_attributeByMemberInfoAndTypeCache.GetOrAdd((memberInfo, typeof(T)), tuple => memberInfo.GetCustomAttribute(typeof(T))!); - - private static T? GetCustomAttribute(Type type) where T : Attribute => - (T?)_attributeByTypeAndTypeCache.GetOrAdd((type, typeof(T)), tuple => type.GetCustomAttribute(typeof(T))!); - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/BaseConverter.cs b/CryptoExchange.Net/Converters/JsonNet/BaseConverter.cs deleted file mode 100644 index 9422d62..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/BaseConverter.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Base class for enum converters - /// - /// Type of enum to convert - public abstract class BaseConverter: JsonConverter where T: struct - { - /// - /// The enum->string mapping - /// - protected abstract List> Mapping { get; } - private readonly bool _quotes; - - /// - /// ctor - /// - /// - 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; - } - - /// - /// Convert a string value - /// - /// - /// - 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))) - mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase)); - - if (!mapping.Equals(default(KeyValuePair))) - { - result = mapping.Key; - return true; - } - - result = default; - return false; - } - - private string GetValue(T value) - { - return Mapping.FirstOrDefault(v => v.Key.Equals(value)).Value; - } - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/BigDecimalConverter.cs b/CryptoExchange.Net/Converters/JsonNet/BigDecimalConverter.cs deleted file mode 100644 index 80777c1..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/BigDecimalConverter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Globalization; -using Newtonsoft.Json; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue) - /// - public class BigDecimalConverter : JsonConverter - { - /// - public override bool CanConvert(Type objectType) - { - if (Nullable.GetUnderlyingType(objectType) != null) - return Nullable.GetUnderlyingType(objectType) == typeof(decimal); - return objectType == typeof(decimal); - } - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return null; - - if (reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Integer) - { - try - { - return decimal.Parse(reader.Value!.ToString()!, NumberStyles.Float, CultureInfo.InvariantCulture); - } - catch (OverflowException) - { - // Value doesn't fit decimal; set it to max value - return decimal.MaxValue; - } - } - - if (reader.TokenType == JsonToken.String) - { - try - { - var value = reader.Value!.ToString()!; - return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); - } - catch (OverflowException) - { - // Value doesn't fit decimal; set it to max value - return decimal.MaxValue; - } - } - - return null; - } - - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - writer.WriteValue(value); - } - } -} \ No newline at end of file diff --git a/CryptoExchange.Net/Converters/JsonNet/BoolConverter.cs b/CryptoExchange.Net/Converters/JsonNet/BoolConverter.cs deleted file mode 100644 index 4f519dd..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/BoolConverter.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Boolean converter with support for "0"/"1" (strings) - /// - public class BoolConverter : JsonConverter - { - /// - /// Determines whether this instance can convert the specified object type. - /// - /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// - public override bool CanConvert(Type objectType) - { - if (Nullable.GetUnderlyingType(objectType) != null) - return Nullable.GetUnderlyingType(objectType) == typeof(bool); - return objectType == typeof(bool); - } - - /// - /// Reads the JSON representation of the object. - /// - /// The to read from. - /// Type of the object. - /// The existing value of object being read. - /// The calling serializer. - /// - /// The object value. - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - var value = reader.Value?.ToString()!.ToLower().Trim(); - if (value == null || value == "") - { - if (Nullable.GetUnderlyingType(objectType) != null) - return null; - - return false; - } - - switch (value) - { - case "true": - case "yes": - case "y": - case "1": - case "on": - return true; - case "false": - case "no": - case "n": - case "0": - case "off": - case "-1": - return false; - } - - // If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message. - return new JsonSerializer().Deserialize(reader, objectType); - } - - /// - /// Specifies that this converter will not participate in writing results. - /// - public override bool CanWrite { get { return false; } } - - /// - /// Writes the JSON representation of the object. - /// - /// The to write to.The value.The calling serializer. - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - } - } -} \ No newline at end of file diff --git a/CryptoExchange.Net/Converters/JsonNet/DateTimeConverter.cs b/CryptoExchange.Net/Converters/JsonNet/DateTimeConverter.cs deleted file mode 100644 index e2f686a..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/DateTimeConverter.cs +++ /dev/null @@ -1,240 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Datetime converter. Supports converting from string/long/double to DateTime and back. Numbers are assumed to be the time since 1970-01-01. - /// - public class DateTimeConverter: JsonConverter - { - private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000; - private const decimal _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; - private const decimal _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000m / 1000; - - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(DateTime) || objectType == typeof(DateTime?); - } - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (reader.Value == null) - { - if (objectType == typeof(DateTime)) - return default(DateTime); - - return null; - } - - if(reader.TokenType is JsonToken.Integer) - { - var longValue = (long)reader.Value; - if (longValue == 0 || longValue == -1) - return objectType == typeof(DateTime) ? default(DateTime): null; - - return ParseFromLong(longValue); - } - else if (reader.TokenType is JsonToken.Float) - { - var doubleValue = (double)reader.Value; - if (doubleValue == 0 || doubleValue == -1) - return objectType == typeof(DateTime) ? default(DateTime) : null; - - if (doubleValue < 19999999999) - return ConvertFromSeconds(doubleValue); - - return ConvertFromMilliseconds(doubleValue); - } - else if(reader.TokenType is JsonToken.String) - { - var stringValue = (string)reader.Value; - if (string.IsNullOrWhiteSpace(stringValue) - || stringValue == "-1" - || (double.TryParse(stringValue, out var doubleVal) && doubleVal == 0)) - { - return objectType == typeof(DateTime) ? default(DateTime) : null; - } - - return ParseFromString(stringValue); - } - else if(reader.TokenType == JsonToken.Date) - { - return (DateTime)reader.Value; - } - else - { - Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + reader.Value); - return default; - } - } - - /// - /// Parse a long value to datetime - /// - /// - /// - public static DateTime ParseFromLong(long longValue) - { - if (longValue < 19999999999) - return ConvertFromSeconds(longValue); - if (longValue < 19999999999999) - return ConvertFromMilliseconds(longValue); - if (longValue < 19999999999999999) - return ConvertFromMicroseconds(longValue); - - return ConvertFromNanoseconds(longValue); - } - - /// - /// Parse a string value to datetime - /// - /// - /// - public static DateTime ParseFromString(string stringValue) - { - if (stringValue.Length == 12 && stringValue.StartsWith("202")) - { - // Parse 202303261200 format - if (!int.TryParse(stringValue.Substring(0, 4), out var year) - || !int.TryParse(stringValue.Substring(4, 2), out var month) - || !int.TryParse(stringValue.Substring(6, 2), out var day) - || !int.TryParse(stringValue.Substring(8, 2), out var hour) - || !int.TryParse(stringValue.Substring(10, 2), out var minute)) - { - Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); - return default; - } - return new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Utc); - } - - if (stringValue.Length == 8) - { - // Parse 20211103 format - if (!int.TryParse(stringValue.Substring(0, 4), out var year) - || !int.TryParse(stringValue.Substring(4, 2), out var month) - || !int.TryParse(stringValue.Substring(6, 2), out var day)) - { - Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); - return default; - } - return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); - } - - if (stringValue.Length == 6) - { - // Parse 211103 format - if (!int.TryParse(stringValue.Substring(0, 2), out var year) - || !int.TryParse(stringValue.Substring(2, 2), out var month) - || !int.TryParse(stringValue.Substring(4, 2), out var day)) - { - Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); - return default; - } - return new DateTime(year + 2000, month, day, 0, 0, 0, DateTimeKind.Utc); - } - - if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue)) - { - // Parse 1637745563.000 format - if (doubleValue < 19999999999) - return ConvertFromSeconds(doubleValue); - if (doubleValue < 19999999999999) - return ConvertFromMilliseconds((long)doubleValue); - if (doubleValue < 19999999999999999) - return ConvertFromMicroseconds((long)doubleValue); - - return ConvertFromNanoseconds((long)doubleValue); - } - - if (stringValue.Length == 10) - { - // Parse 2021-11-03 format - var values = stringValue.Split('-'); - if (!int.TryParse(values[0], out var year) - || !int.TryParse(values[1], out var month) - || !int.TryParse(values[2], out var day)) - { - Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); - return default; - } - - return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); - } - - return DateTime.Parse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - } - - /// - /// Convert a seconds since epoch (01-01-1970) value to DateTime - /// - /// - /// - public static DateTime ConvertFromSeconds(double seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond)); - /// - /// Convert a milliseconds since epoch (01-01-1970) value to DateTime - /// - /// - /// - public static DateTime ConvertFromMilliseconds(double milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond)); - /// - /// Convert a microseconds since epoch (01-01-1970) value to DateTime - /// - /// - /// - public static DateTime ConvertFromMicroseconds(long microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond)); - /// - /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime - /// - /// - /// - public static DateTime ConvertFromNanoseconds(long nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond)); - /// - /// Convert a DateTime value to seconds since epoch (01-01-1970) value - /// - /// - /// - [return: NotNullIfNotNull("time")] - public static long? ConvertToSeconds(DateTime? time) => time == null ? null: (long)Math.Round((time.Value - _epoch).TotalSeconds); - /// - /// Convert a DateTime value to milliseconds since epoch (01-01-1970) value - /// - /// - /// - [return: NotNullIfNotNull("time")] - public static long? ConvertToMilliseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalMilliseconds); - /// - /// Convert a DateTime value to microseconds since epoch (01-01-1970) value - /// - /// - /// - [return: NotNullIfNotNull("time")] - public static long? ConvertToMicroseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerMicrosecond); - /// - /// Convert a DateTime value to nanoseconds since epoch (01-01-1970) value - /// - /// - /// - [return: NotNullIfNotNull("time")] - public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond); - - - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - var datetimeValue = (DateTime?)value; - if (datetimeValue == null) - writer.WriteValue((DateTime?)null); - if(datetimeValue == default(DateTime)) - writer.WriteValue((DateTime?)null); - else - writer.WriteValue((long)Math.Round(((DateTime)value! - new DateTime(1970, 1, 1)).TotalMilliseconds)); - } - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/DecimalStringWriterConverter.cs b/CryptoExchange.Net/Converters/JsonNet/DecimalStringWriterConverter.cs deleted file mode 100644 index 694b3a9..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/DecimalStringWriterConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Globalization; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Converter for serializing decimal values as string - /// - public class DecimalStringWriterConverter : JsonConverter - { - /// - public override bool CanRead => false; - - /// - public override bool CanConvert(Type objectType) => objectType == typeof(decimal) || objectType == typeof(decimal?); - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => writer.WriteValue(((decimal?)value)?.ToString(CultureInfo.InvariantCulture) ?? null); - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/EnumConverter.cs b/CryptoExchange.Net/Converters/JsonNet/EnumConverter.cs deleted file mode 100644 index edd97a0..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/EnumConverter.cs +++ /dev/null @@ -1,176 +0,0 @@ -using CryptoExchange.Net.Attributes; -using Newtonsoft.Json; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value - /// - public class EnumConverter : JsonConverter - { - private bool _warnOnMissingEntry = true; - private bool _writeAsInt; - - /// - /// - public EnumConverter() { } - - /// - /// - /// - /// - public EnumConverter(bool writeAsInt, bool warnOnMissingEntry) - { - _writeAsInt = writeAsInt; - _warnOnMissingEntry = warnOnMissingEntry; - } - - private static readonly ConcurrentDictionary>> _mapping = new(); - - /// - public override bool CanConvert(Type objectType) - { - return objectType.IsEnum || Nullable.GetUnderlyingType(objectType)?.IsEnum == true; - } - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - var enumType = Nullable.GetUnderlyingType(objectType) ?? objectType; - if (!_mapping.TryGetValue(enumType, out var mapping)) - mapping = AddMapping(enumType); - - var stringValue = reader.Value?.ToString(); - if (stringValue == null || stringValue == "") - { - // Received null value - var emptyResult = GetDefaultValue(objectType, enumType); - if(emptyResult != null) - // If the property we're parsing to isn't nullable there isn't a correct way to return this as null will either throw an exception (.net framework) or the default enum value (dotnet core). - Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null enum value, but property type is not a nullable enum. EnumType: {enumType.Name}. If you think {enumType.Name} should be nullable please open an issue on the Github repo"); - - return emptyResult; - } - - if (!GetValue(enumType, mapping, stringValue!, out var result)) - { - var defaultValue = GetDefaultValue(objectType, enumType); - if (string.IsNullOrWhiteSpace(stringValue)) - { - if (defaultValue != null) - // We received an empty string and have no mapping for it, and the property isn't nullable - Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {enumType.Name}. If you think {enumType.Name} should be nullable please open an issue on the Github repo"); - } - else - { - // We received an enum value but weren't able to parse it. - if (_warnOnMissingEntry) - Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, 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 defaultValue; - } - - return result; - } - - private static object? GetDefaultValue(Type objectType, Type enumType) - { - if (Nullable.GetUnderlyingType(objectType) != null) - return null; - - return Activator.CreateInstance(enumType); // return default value - } - - private static List> AddMapping(Type objectType) - { - var mapping = new List>(); - var enumMembers = objectType.GetMembers(); - foreach (var member in enumMembers) - { - var maps = member.GetCustomAttributes(typeof(MapAttribute), false); - foreach (MapAttribute attribute in maps) - { - foreach (var value in attribute.Values) - mapping.Add(new KeyValuePair(Enum.Parse(objectType, member.Name), value)); - } - } - _mapping.TryAdd(objectType, mapping); - return mapping; - } - - private static bool GetValue(Type objectType, List> enumMapping, string value, out object? result) - { - // Check for exact match first, then if not found fallback to a case insensitive match - var mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture)); - if (mapping.Equals(default(KeyValuePair))) - mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase)); - - if (!mapping.Equals(default(KeyValuePair))) - { - result = mapping.Key; - return true; - } - - try - { - // If no explicit mapping is found try to parse string - result = Enum.Parse(objectType, value, true); - return true; - } - catch (Exception) - { - result = default; - return false; - } - } - - /// - /// Get the string value for an enum value using the MapAttribute mapping. When multiple values are mapped for a enum entry the first value will be returned - /// - /// - /// - /// - [return: NotNullIfNotNull("enumValue")] - public static string? GetString(T enumValue) => GetString(typeof(T), enumValue); - - - [return: NotNullIfNotNull("enumValue")] - private static string? GetString(Type objectType, object? enumValue) - { - objectType = Nullable.GetUnderlyingType(objectType) ?? objectType; - - if (!_mapping.TryGetValue(objectType, out var mapping)) - mapping = AddMapping(objectType); - - return enumValue == null ? null : (mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString()); - } - - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value == null) - { - writer.WriteNull(); - } - else - { - if (!_writeAsInt) - { - var stringValue = GetString(value.GetType(), value); - writer.WriteValue(stringValue); - } - else - { - writer.WriteValue((int)value); - } - } - } - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageAccessor.cs b/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageAccessor.cs deleted file mode 100644 index 2938ee4..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageAccessor.cs +++ /dev/null @@ -1,338 +0,0 @@ -using CryptoExchange.Net.Converters.MessageParsing; -using CryptoExchange.Net.Interfaces; -using CryptoExchange.Net.Objects; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Json.Net message accessor - /// - public abstract class JsonNetMessageAccessor : IMessageAccessor - { - /// - /// The json token loaded - /// - protected JToken? _token; - private static readonly JsonSerializer _serializer = JsonSerializer.Create(SerializerOptions.WithConverters); - - /// - public bool IsJson { get; protected set; } - - /// - public abstract bool OriginalDataAvailable { get; } - - /// - public object? Underlying => _token; - - /// - public CallResult Deserialize(Type type, MessagePath? path = null) - { - if (!IsJson) - return new CallResult(GetOriginalString()); - - var source = _token; - if (path != null) - source = GetPathNode(path.Value); - - try - { - var result = source!.ToObject(type, _serializer)!; - return new CallResult(result); - } - catch (JsonReaderException jre) - { - var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - catch (JsonSerializationException jse) - { - var info = $"Deserialize JsonSerializationException: {jse.Message}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - catch (Exception ex) - { - var exceptionInfo = ex.ToLogString(); - var info = $"Deserialize Unknown Exception: {exceptionInfo}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - } - - /// - public CallResult Deserialize(MessagePath? path = null) - { - var source = _token; - if (path != null) - source = GetPathNode(path.Value); - - try - { - var result = source!.ToObject(_serializer)!; - return new CallResult(result); - } - catch (JsonReaderException jre) - { - var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - catch (JsonSerializationException jse) - { - var info = $"Deserialize JsonSerializationException: {jse.Message}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - catch (Exception ex) - { - var exceptionInfo = ex.ToLogString(); - var info = $"Deserialize Unknown Exception: {exceptionInfo}"; - return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); - } - } - - /// - public NodeType? GetNodeType() - { - if (!IsJson) - throw new InvalidOperationException("Can't access json data on non-json message"); - - if (_token == null) - return null; - - if (_token.Type == JTokenType.Object) - return NodeType.Object; - - if (_token.Type == JTokenType.Array) - return NodeType.Array; - - return NodeType.Value; - } - - /// - public NodeType? GetNodeType(MessagePath path) - { - if (!IsJson) - throw new InvalidOperationException("Can't access json data on non-json message"); - - var node = GetPathNode(path); - if (node == null) - return null; - - if (node.Type == JTokenType.Object) - return NodeType.Object; - - if (node.Type == JTokenType.Array) - return NodeType.Array; - - return NodeType.Value; - } - - /// - public T? GetValue(MessagePath path) - { - if (!IsJson) - throw new InvalidOperationException("Can't access json data on non-json message"); - - var value = GetPathNode(path); - if (value == null) - return default; - - if (value.Type == JTokenType.Object || value.Type == JTokenType.Array) - return default; - - return value!.Value(); - } - - /// - public List? GetValues(MessagePath path) - { - if (!IsJson) - throw new InvalidOperationException("Can't access json data on non-json message"); - - var value = GetPathNode(path); - if (value == null) - return default; - - if (value.Type == JTokenType.Object) - return default; - - return value!.Values().ToList(); - } - - private JToken? GetPathNode(MessagePath path) - { - if (!IsJson) - throw new InvalidOperationException("Can't access json data on non-json message"); - - var currentToken = _token; - foreach (var node in path) - { - if (node.Type == 0) - { - // Int value - var val = node.Index!.Value; - if (currentToken!.Type != JTokenType.Array || ((JArray)currentToken).Count <= val) - return null; - - currentToken = currentToken[val]; - } - else if (node.Type == 1) - { - // String value - if (currentToken!.Type != JTokenType.Object) - return null; - - currentToken = currentToken[node.Property!]; - } - else - { - // Property name - if (currentToken!.Type != JTokenType.Object) - return null; - - currentToken = (currentToken.First as JProperty)?.Name; - } - - if (currentToken == null) - return null; - } - - return currentToken; - } - - /// - public abstract string GetOriginalString(); - - /// - public abstract void Clear(); - } - - /// - /// Json.Net stream message accessor - /// - public class JsonNetStreamMessageAccessor : JsonNetMessageAccessor, IStreamMessageAccessor - { - private Stream? _stream; - - /// - public override bool OriginalDataAvailable => _stream?.CanSeek == true; - - /// - public async Task Read(Stream stream, bool bufferStream) - { - if (bufferStream && stream is not MemoryStream) - { - // We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream - _stream = new MemoryStream(); - stream.CopyTo(_stream); - _stream.Position = 0; - } - else if (bufferStream) - { - // We need to buffer the stream, and the current stream is seekable, store as is - _stream = stream; - } - else - { - // We don't need to buffer the stream, so don't bother keeping the reference - } - - var readStream = _stream ?? stream; - var length = readStream.CanSeek ? readStream.Length : 4096; - using var reader = new StreamReader(readStream, Encoding.UTF8, false, (int)Math.Max(2, length), true); - using var jsonTextReader = new JsonTextReader(reader); - - try - { - _token = await JToken.LoadAsync(jsonTextReader).ConfigureAwait(false); - IsJson = true; - return new CallResult(null); - } - catch (Exception ex) - { - // Not a json message - IsJson = false; - return new CallResult(new ServerError("JsonError: " + ex.Message)); - } - - } - /// - public override string GetOriginalString() - { - if (_stream is null) - throw new NullReferenceException("Stream not initialized"); - - _stream.Position = 0; - using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true); - return textReader.ReadToEnd(); - } - - /// - public override void Clear() - { - _stream?.Dispose(); - _stream = null; - _token = null; - } - - } - - /// - /// Json.Net byte message accessor - /// - public class JsonNetByteMessageAccessor : JsonNetMessageAccessor, IByteMessageAccessor - { - private ReadOnlyMemory _bytes; - - /// - public CallResult Read(ReadOnlyMemory data) - { - _bytes = data; - - // Try getting the underlying byte[] instead of the ToArray to prevent creating a copy - using var stream = MemoryMarshal.TryGetArray(data, out var arraySegment) - ? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count) - : new MemoryStream(data.ToArray()); - using var reader = new StreamReader(stream, Encoding.UTF8, false, Math.Max(2, data.Length), true); - using var jsonTextReader = new JsonTextReader(reader); - - try - { - _token = JToken.Load(jsonTextReader); - IsJson = true; - return new CallResult(null); - } - catch (Exception ex) - { - // Not a json message - IsJson = false; - return new CallResult(new ServerError("JsonError: " + ex.Message)); - } - } - - /// - public override string GetOriginalString() => - // Netstandard 2.0 doesn't support GetString from a ReadonlySpan, so use ToArray there instead -#if NETSTANDARD2_0 - Encoding.UTF8.GetString(_bytes.ToArray()); -#else - Encoding.UTF8.GetString(_bytes.Span); -#endif - - /// - public override bool OriginalDataAvailable => true; - - /// - public override void Clear() - { - _bytes = null; - _token = null; - } - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageSerializer.cs b/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageSerializer.cs deleted file mode 100644 index 2388a8d..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/JsonNetMessageSerializer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using CryptoExchange.Net.Interfaces; -using Newtonsoft.Json; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - public class JsonNetMessageSerializer : IMessageSerializer - { - /// - public string Serialize(T message) => JsonConvert.SerializeObject(message, Formatting.None); - } -} diff --git a/CryptoExchange.Net/Converters/JsonNet/SerializerOptions.cs b/CryptoExchange.Net/Converters/JsonNet/SerializerOptions.cs deleted file mode 100644 index 96756e8..0000000 --- a/CryptoExchange.Net/Converters/JsonNet/SerializerOptions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Newtonsoft.Json; -using System.Globalization; - -namespace CryptoExchange.Net.Converters.JsonNet -{ - /// - /// Serializer options - /// - public static class SerializerOptions - { - /// - /// Json serializer settings which includes the EnumConverter, DateTimeConverter and BoolConverter - /// - public static JsonSerializerSettings WithConverters => new JsonSerializerSettings - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - Culture = CultureInfo.InvariantCulture, - Converters = - { - new EnumConverter(), - new DateTimeConverter(), - new BoolConverter() - } - }; - - /// - /// Default json serializer settings - /// - public static JsonSerializerSettings Default => new JsonSerializerSettings - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - Culture = CultureInfo.InvariantCulture - }; - } -} diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index bb0957e..273e67e 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1;net8.0;net9.0 @@ -55,10 +55,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + diff --git a/CryptoExchange.Net/Testing/Comparers/JsonNetComparer.cs b/CryptoExchange.Net/Testing/Comparers/JsonNetComparer.cs deleted file mode 100644 index 3893b33..0000000 --- a/CryptoExchange.Net/Testing/Comparers/JsonNetComparer.cs +++ /dev/null @@ -1,386 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Reflection; -using CryptoExchange.Net.Converters; -using CryptoExchange.Net.Converters.JsonNet; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace CryptoExchange.Net.Testing.Comparers -{ - internal class JsonNetComparer - { - internal static void CompareData( - string method, - object resultData, - string json, - string? nestedJsonProperty, - List? ignoreProperties = null, - bool userSingleArrayItem = false) - { - var resultProperties = resultData.GetType().GetProperties().Select(p => (p, (JsonPropertyAttribute?)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).SingleOrDefault())); - var jsonObject = JToken.Parse(json); - if (nestedJsonProperty != null) - { - var nested = nestedJsonProperty.Split('.'); - foreach (var nest in nested) - { - if (int.TryParse(nest, out var index)) - jsonObject = jsonObject![index]; - else - jsonObject = jsonObject![nest]; - } - } - - if (userSingleArrayItem) - jsonObject = ((JArray)jsonObject!)[0]; - - if (resultData.GetType().GetInterfaces().Contains(typeof(IDictionary))) - { - var dict = (IDictionary)resultData; - var jObj = (JObject)jsonObject!; - var properties = jObj.Properties(); - foreach (var dictProp in properties) - { - if (!dict.Contains(dictProp.Name)) - throw new Exception($"{method}: Dictionary has no value for {dictProp.Name} while input json `{dictProp.Name}` has value {dictProp.Value}"); - - if (dictProp.Value.Type == JTokenType.Object) - { - // TODO Some additional checking for objects - foreach (var prop in ((JObject)dictProp.Value).Properties()) - CheckObject(method, prop, dict[dictProp.Name]!, ignoreProperties!); - } - else - { - if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null) - // Property value not correct - throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}"); - } - } - } - else if (jsonObject!.Type == JTokenType.Array) - { - var jObjs = (JArray)jsonObject; - if (resultData is IEnumerable list) - { - var enumerator = list.GetEnumerator(); - foreach (var jObj in jObjs) - { - enumerator.MoveNext(); - if (jObj.Type == JTokenType.Object) - { - foreach (var subProp in ((JObject)jObj).Properties()) - { - if (ignoreProperties?.Contains(subProp.Name) == true) - continue; - CheckObject(method, subProp, enumerator.Current, ignoreProperties!); - } - } - else if (jObj.Type == JTokenType.Array) - { - var resultObj = enumerator.Current; - if (resultObj is string) - // string list - continue; - - var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); - var arrayConverterProperty = resultObj.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true).FirstOrDefault(); - var jsonConverter = ((JsonConverterAttribute)arrayConverterProperty!).ConverterType; - if (jsonConverter != typeof(ArrayConverter)) - // Not array converter? - continue; - - int i = 0; - foreach (var item in jObj.Children()) - { - var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p; - if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); - i++; - } - } - else - { - var value = enumerator.Current; - if (value == default && ((JValue)jObj).Type != JTokenType.Null) - throw new Exception($"{method}: Array has no value while input json array has value {jObj}"); - } - } - } - else - { - var resultProps = resultData.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); - int i = 0; - foreach (var item in jObjs.Children()) - { - var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p; - if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(resultData), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); - i++; - } - } - } - else - { - foreach (var item in jsonObject) - { - if (item is JProperty prop) - { - if (ignoreProperties?.Contains(prop.Name) == true) - continue; - - CheckObject(method, prop, resultData, ignoreProperties); - } - } - } - - Debug.WriteLine($"Successfully validated {method}"); - } - - private static void CheckObject(string method, JProperty prop, object obj, List? ignoreProperties) - { - var resultProperties = obj.GetType().GetProperties().Select(p => (p, ((JsonPropertyAttribute?)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).SingleOrDefault())?.PropertyName)); - - // Property has a value - var property = resultProperties.SingleOrDefault(p => p.PropertyName == prop.Name).p; - property ??= resultProperties.SingleOrDefault(p => p.p.Name == prop.Name).p; - property ??= resultProperties.SingleOrDefault(p => p.p.Name.Equals(prop.Name, StringComparison.InvariantCultureIgnoreCase)).p; - - if (property is null) - // Property not found - throw new Exception($"{method}: Missing property `{prop.Name}` on `{obj.GetType().Name}`"); - - var propertyValue = property.GetValue(obj); - if (property.GetCustomAttribute(true)?.ItemConverterType == null) - CheckPropertyValue(method, prop.Value, propertyValue, property.PropertyType, property.Name, prop.Name, ignoreProperties); - } - - private static void CheckPropertyValue(string method, JToken propValue, object? propertyValue, Type propertyType, string? propertyName = null, string? propName = null, List? ignoreProperties = null) - { - if (propertyValue == default && propValue.Type != JTokenType.Null && !string.IsNullOrEmpty(propValue.ToString())) - { - if (propertyType == typeof(DateTime?) && (propValue.ToString() == "" || propValue.ToString() == "0" || propValue.ToString() == "-1")) - return; - - // Property value not correct - if (propValue.ToString() != "0") - throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}"); - } - - if ((propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString()))) || propValue.ToString() == "0") - return; - - if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary))) - { - var dict = (IDictionary)propertyValue; - var jObj = (JObject)propValue; - var properties = jObj.Properties(); - foreach (var dictProp in properties) - { - if (!dict.Contains(dictProp.Name)) - throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}"); - - if (dictProp.Value.Type == JTokenType.Object) - { - CheckObject(method, dictProp, dict[dictProp.Name]!, ignoreProperties); - } - else - { - if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null) - // Property value not correct - throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {propValue} for"); - } - } - } - else if (propertyValue.GetType().GetInterfaces().Contains(typeof(IEnumerable)) - && propertyValue.GetType() != typeof(string)) - { - var jObjs = (JArray)propValue; - var list = (IEnumerable)propertyValue; - var enumerator = list.GetEnumerator(); - foreach (JToken jtoken in jObjs) - { - enumerator.MoveNext(); - var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true); - if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter)) - // Custom converter for the type, skip - continue; - - if (jtoken.Type == JTokenType.Object) - { - foreach (var subProp in ((JObject)jtoken).Properties()) - { - if (ignoreProperties?.Contains(subProp.Name) == true) - continue; - - CheckObject(method, subProp, enumerator.Current, ignoreProperties); - } - } - else if (jtoken.Type == JTokenType.Array) - { - var resultObj = enumerator.Current; - var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); - var arrayConverterProperty = resultObj.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true).FirstOrDefault(); - var jsonConverter = ((JsonConverterAttribute)arrayConverterProperty!).ConverterType; - if (jsonConverter != typeof(ArrayConverter)) - // Not array converter? - continue; - - int i = 0; - foreach (var item in jtoken.Children()) - { - var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p; - if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); - - i++; - } - } - else - { - var value = enumerator.Current; - if (value == default && ((JValue)jtoken).Type != JTokenType.Null) - throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {jtoken}"); - - CheckValues(method, propertyName!, propertyType, (JValue)jtoken, value!); - } - } - } - else - { - if (propValue.Type == JTokenType.Object) - { - foreach (var item in propValue) - { - if (item is JProperty prop) - { - if (ignoreProperties?.Contains(prop.Name) == true) - continue; - - CheckObject(method, prop, propertyValue, ignoreProperties); - } - } - } - else if(propValue.Type == JTokenType.Array) - { - var jObjs = (JArray)propValue; - if (propertyValue is IEnumerable list) - { - var enumerator = list.GetEnumerator(); - foreach (var jObj in jObjs) - { - if (!enumerator.MoveNext()) - { - } - - if (jObj.Type == JTokenType.Object) - { - foreach (var subProp in ((JObject)jObj).Properties()) - { - if (ignoreProperties?.Contains(subProp.Name) == true) - continue; - CheckObject(method, subProp, enumerator.Current, ignoreProperties!); - } - } - else if (jObj.Type == JTokenType.Array) - { - var resultObj = enumerator.Current; - var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); - var arrayConverterProperty = resultObj.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true).FirstOrDefault(); - var jsonConverter = ((JsonConverterAttribute)arrayConverterProperty!).ConverterType; - if (jsonConverter != typeof(ArrayConverter)) - // Not array converter? - continue; - - int i = 0; - foreach (var item in jObj.Values()) - { - var arrayProp = resultProps.SingleOrDefault(p => p.Item2!.Index == i).p; - if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); - i++; - } - } - else - { - var value = enumerator.Current; - if (value == default && ((JValue)jObj).Type != JTokenType.Null) - throw new Exception($"{method}: Array has no value while input json array has value {jObj}"); - } - } - } - else - { - var resultProps = propertyValue.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); - int i = 0; - foreach (var item in jObjs.Children()) - { - var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p; - if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(propertyValue), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); - i++; - } - } - } - else - { - CheckValues(method, propertyName!, propertyType, (JValue)propValue, propertyValue); - } - } - } - - private static void CheckValues(string method, string property, Type propertyType, JValue jsonValue, object objectValue) - { - if (jsonValue.Type == JTokenType.String) - { - if (objectValue is decimal dec) - { - if (jsonValue.Value() != dec) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {dec}"); - } - else if (objectValue is DateTime time) - { - if (time != DateTimeConverter.ParseFromString(jsonValue.Value()!)) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {time}"); - } - else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true) - { - // TODO enum comparing - } - else if (!jsonValue.Value()!.Equals(Convert.ToString(objectValue, CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)) - { - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {objectValue}"); - } - } - else if (jsonValue.Type == JTokenType.Integer) - { - if (objectValue is DateTime time) - { - if (time != DateTimeConverter.ParseFromLong(jsonValue.Value()!)) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {time}"); - } - else if (propertyType.IsEnum) - { - // TODO enum comparing - } - else if (jsonValue.Value() != Convert.ToInt64(objectValue)) - { - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {Convert.ToInt64(objectValue)}"); - } - } - else if (jsonValue.Type == JTokenType.Boolean) - { - if (objectValue is bool boolVal && jsonValue.Value() != boolVal) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {(bool)objectValue}"); - - if (jsonValue.Value() != bool.Parse(objectValue.ToString()!)) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {(bool)objectValue}"); - } - } - } -} diff --git a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs index 46c27c7..32243a3 100644 --- a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs +++ b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs @@ -4,10 +4,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using CryptoExchange.Net.Converters; using CryptoExchange.Net.Converters.SystemTextJson; -using Newtonsoft.Json.Linq; namespace CryptoExchange.Net.Testing.Comparers { @@ -22,7 +23,7 @@ namespace CryptoExchange.Net.Testing.Comparers bool userSingleArrayItem = false) { var resultProperties = resultData.GetType().GetProperties().Select(p => (p, (JsonPropertyNameAttribute?)p.GetCustomAttributes(typeof(JsonPropertyNameAttribute), true).SingleOrDefault())); - var jsonObject = JToken.Parse(json); + var jsonObject = JsonDocument.Parse(json).RootElement; if (nestedJsonProperty != null) { var nested = nestedJsonProperty.Split('.'); @@ -31,32 +32,31 @@ namespace CryptoExchange.Net.Testing.Comparers if (int.TryParse(nest, out var index)) jsonObject = jsonObject![index]; else - jsonObject = jsonObject![nest]; + jsonObject = jsonObject!.GetProperty(nest); } } if (userSingleArrayItem) - jsonObject = ((JArray)jsonObject!)[0]; + jsonObject = jsonObject[0]; if (resultData.GetType().GetInterfaces().Contains(typeof(IDictionary))) { var dict = (IDictionary)resultData; - var jObj = (JObject)jsonObject!; - var properties = jObj.Properties(); - foreach (var dictProp in properties) + var jObj = jsonObject!; + foreach (var dictProp in jObj.EnumerateObject()) { if (!dict.Contains(dictProp.Name)) throw new Exception($"{method}: Dictionary has no value for {dictProp.Name} while input json `{dictProp.Name}` has value {dictProp.Value}"); - if (dictProp.Value.Type == JTokenType.Object) + if (dictProp.Value.ValueKind == JsonValueKind.Object) { // TODO Some additional checking for objects - foreach (var prop in ((JObject)dictProp.Value).Properties()) + foreach (var prop in dictProp.Value.EnumerateObject()) CheckObject(method, prop, dict[dictProp.Name]!, ignoreProperties!); } else { - if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null) + if (dict[dictProp.Name] == default && dictProp.Value.ValueKind != JsonValueKind.Null) { if (dictProp.Value.ToString() == "") continue; @@ -67,28 +67,27 @@ namespace CryptoExchange.Net.Testing.Comparers } } } - else if (jsonObject!.Type == JTokenType.Array) + else if (jsonObject!.ValueKind == JsonValueKind.Array) { - var jArray = (JArray)jsonObject; if (resultData is IEnumerable list) { var enumerator = list.GetEnumerator(); - foreach (var jObj in jArray) + foreach (var jObj in jsonObject.EnumerateArray()) { if (!enumerator.MoveNext()) { } - if (jObj.Type == JTokenType.Object) + if (jObj.ValueKind == JsonValueKind.Object) { - foreach (var subProp in ((JObject)jObj).Properties()) + foreach (var subProp in jObj.EnumerateObject()) { if (ignoreProperties?.Contains(subProp.Name) == true) continue; CheckObject(method, subProp, enumerator.Current, ignoreProperties!); } } - else if (jObj.Type == JTokenType.Array) + else if (jObj.ValueKind == JsonValueKind.Array) { var resultObj = enumerator.Current; if (resultObj is string) @@ -103,18 +102,18 @@ namespace CryptoExchange.Net.Testing.Comparers continue; int i = 0; - foreach (var item in jObj.Children()) + foreach (var item in jObj.EnumerateObject()) { var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p; if (arrayProp != null) - CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); + CheckPropertyValue(method, item.Value, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!); i++; } } else { var value = enumerator.Current; - if (value == default && ((JValue)jObj).Type != JTokenType.Null) + if (value == default && jObj.ValueKind != JsonValueKind.Null) throw new Exception($"{method}: Array has no value while input json array has value {jObj}"); } } @@ -123,7 +122,7 @@ namespace CryptoExchange.Net.Testing.Comparers { var resultProps = resultData.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); int i = 0; - foreach (var item in jArray.Children()) + foreach (var item in jsonObject.EnumerateArray()) { var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p; if (arrayProp != null) @@ -134,22 +133,22 @@ namespace CryptoExchange.Net.Testing.Comparers } else { - foreach (var item in jsonObject) + foreach (var item in jsonObject.EnumerateObject()) { - if (item is JProperty prop) - { - if (ignoreProperties?.Contains(prop.Name) == true) + //if (item is JProperty prop) + //{ + if (ignoreProperties?.Contains(item.Name) == true) continue; - CheckObject(method, prop, resultData, ignoreProperties); - } + CheckObject(method, item, resultData, ignoreProperties); + //} } } Debug.WriteLine($"Successfully validated {method}"); } - private static void CheckObject(string method, JProperty prop, object obj, List? ignoreProperties) + private static void CheckObject(string method, JsonProperty prop, object obj, List? ignoreProperties) { var publicProperties = obj.GetType().GetProperties( System.Reflection.BindingFlags.Public @@ -184,9 +183,9 @@ namespace CryptoExchange.Net.Testing.Comparers CheckPropertyValue(method, prop.Value, propertyValue, property.PropertyType, property.Name, prop.Name, ignoreProperties); } - private static void CheckPropertyValue(string method, JToken propValue, object? propertyValue, Type propertyType, string? propertyName = null, string? propName = null, List? ignoreProperties = null) + private static void CheckPropertyValue(string method, JsonElement propValue, object? propertyValue, Type propertyType, string? propertyName = null, string? propName = null, List? ignoreProperties = null) { - if (propertyValue == default && propValue.Type != JTokenType.Null && !string.IsNullOrEmpty(propValue.ToString())) + if (propertyValue == default && propValue.ValueKind != JsonValueKind.Null && !string.IsNullOrEmpty(propValue.ToString())) { if (propertyType == typeof(DateTime?) && (propValue.ToString() == "" || propValue.ToString() == "0" || propValue.ToString() == "-1" || propValue.ToString() == "01/01/0001 00:00:00")) return; @@ -196,26 +195,24 @@ namespace CryptoExchange.Net.Testing.Comparers throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}"); } - if ((propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString()))) || propValue.ToString() == "0") + if ((propertyValue == default && (propValue.ValueKind == JsonValueKind.Null || string.IsNullOrEmpty(propValue.ToString()))) || propValue.ToString() == "0") return; if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary))) { var dict = (IDictionary)propertyValue; - var jObj = (JObject)propValue; - var properties = jObj.Properties(); - foreach (var dictProp in properties) + foreach (var dictProp in propValue.EnumerateObject()) { if (!dict.Contains(dictProp.Name)) throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}"); - if (dictProp.Value.Type == JTokenType.Object) + if (dictProp.Value.ValueKind == JsonValueKind.Object) { CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name]!.GetType(), null, null, ignoreProperties); } else { - if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null) + if (dict[dictProp.Name] == default && dictProp.Value.ValueKind != JsonValueKind.Null) // Property value not correct throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {propValue} for"); } @@ -224,13 +221,12 @@ namespace CryptoExchange.Net.Testing.Comparers else if (propertyValue.GetType().GetInterfaces().Contains(typeof(IEnumerable)) && propertyValue.GetType() != typeof(string)) { - if (propValue.Type != JTokenType.Array) + if (propValue.ValueKind != JsonValueKind.Array) return; - var jArray = (JArray)propValue; var list = (IEnumerable)propertyValue; var enumerator = list.GetEnumerator(); - foreach (JToken jToken in jArray) + foreach (var jToken in propValue.EnumerateArray()) { var moved = enumerator.MoveNext(); if (!moved) @@ -241,9 +237,9 @@ namespace CryptoExchange.Net.Testing.Comparers // Custom converter for the type, skip continue; - if (jToken.Type == JTokenType.Object) + if (jToken.ValueKind == JsonValueKind.Object) { - foreach (var subProp in ((JObject)jToken).Properties()) + foreach (var subProp in jToken.EnumerateObject()) { if (ignoreProperties?.Contains(subProp.Name) == true) continue; @@ -251,7 +247,7 @@ namespace CryptoExchange.Net.Testing.Comparers CheckObject(method, subProp, enumerator.Current, ignoreProperties); } } - else if (jToken.Type == JTokenType.Array) + else if (jToken.ValueKind == JsonValueKind.Array) { var resultObj = enumerator.Current; var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); @@ -262,7 +258,7 @@ namespace CryptoExchange.Net.Testing.Comparers continue; int i = 0; - foreach (var item in jToken.Children()) + foreach (var item in jToken.EnumerateArray()) { var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p; if (arrayProp != null) @@ -274,50 +270,49 @@ namespace CryptoExchange.Net.Testing.Comparers else { var value = enumerator.Current; - if (value == default && ((JValue)jToken).Type != JTokenType.Null) + if (value == default && jToken.ValueKind != JsonValueKind.Null) throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {jToken}"); - CheckValues(method, propertyName!, propertyType, (JValue)jToken, value!); + CheckValues(method, propertyName!, propertyType, jToken, value!); } } } else { - if (propValue.Type == JTokenType.Object) + if (propValue.ValueKind == JsonValueKind.Object) { - foreach (var item in propValue) + foreach (var item in propValue.EnumerateObject()) { - if (item is JProperty prop) - { - if (ignoreProperties?.Contains(prop.Name) == true) + //if (item is JProperty prop) + //{ + if (ignoreProperties?.Contains(item.Name) == true) continue; - CheckObject(method, prop, propertyValue, ignoreProperties); - } + CheckObject(method, item, propertyValue, ignoreProperties); + //} } } - else if (propValue.Type == JTokenType.Array) + else if (propValue.ValueKind == JsonValueKind.Array) { - var jArray = (JArray)propValue; if (propertyValue is IEnumerable list) { var enumerator = list.GetEnumerator(); - foreach (var jObj in jArray) + foreach (var jObj in propValue.EnumerateArray()) { if (!enumerator.MoveNext()) { } - if (jObj.Type == JTokenType.Object) + if (jObj.ValueKind == JsonValueKind.Object) { - foreach (var subProp in ((JObject)jObj).Properties()) + foreach (var subProp in jObj.EnumerateObject()) { if (ignoreProperties?.Contains(subProp.Name) == true) continue; CheckObject(method, subProp, enumerator.Current, ignoreProperties!); } } - else if (jObj.Type == JTokenType.Array) + else if (jObj.ValueKind == JsonValueKind.Array) { var resultObj = enumerator.Current; var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); @@ -328,7 +323,7 @@ namespace CryptoExchange.Net.Testing.Comparers continue; int i = 0; - foreach (var item in jObj.Values()) + foreach (var item in jObj.EnumerateArray()) { var arrayProp = resultProps.SingleOrDefault(p => p.Item2!.Index == i).p; if (arrayProp != null) @@ -339,7 +334,7 @@ namespace CryptoExchange.Net.Testing.Comparers else { var value = enumerator.Current; - if (value == default && ((JValue)jObj).Type != JTokenType.Null) + if (value == default && jObj.ValueKind != JsonValueKind.Null) throw new Exception($"{method}: Array has no value while input json array has value {jObj}"); } } @@ -348,7 +343,7 @@ namespace CryptoExchange.Net.Testing.Comparers { var resultProps = propertyValue.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast().SingleOrDefault())); int i = 0; - foreach (var item in jArray.Children()) + foreach (var item in propValue.EnumerateArray()) { var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p; if (arrayProp != null) @@ -359,63 +354,63 @@ namespace CryptoExchange.Net.Testing.Comparers } else { - CheckValues(method, propertyName!, propertyType, (JValue)propValue, propertyValue); + CheckValues(method, propertyName!, propertyType, propValue, propertyValue); } } } - private static void CheckValues(string method, string property, Type propertyType, JValue jsonValue, object objectValue) + private static void CheckValues(string method, string property, Type propertyType, JsonElement jsonValue, object objectValue) { - if (jsonValue.Type == JTokenType.String) + if (jsonValue.ValueKind == JsonValueKind.String) { + var stringValue = jsonValue.GetString(); if (objectValue is decimal dec) { - if (jsonValue.Value() != dec) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {dec}"); + if (decimal.Parse(stringValue!) != dec) + throw new Exception($"{method}: {property} not equal: {jsonValue.GetDecimal()} vs {dec}"); } else if (objectValue is DateTime time) { - var jsonStr = jsonValue.Value()!; - if (!string.IsNullOrEmpty(jsonStr) && time != DateTimeConverter.ParseFromString(jsonStr)) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {time}"); + if (!string.IsNullOrEmpty(stringValue) && time != DateTimeConverter.ParseFromString(stringValue!)) + throw new Exception($"{method}: {property} not equal: {stringValue} vs {time}"); } else if (objectValue is bool bl) { - var jsonStr = jsonValue.Value(); - if (bl && (jsonStr != "1" && jsonStr != "true" && jsonStr != "True")) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {bl}"); - if (!bl && (jsonStr != "0" && jsonStr != "-1" && jsonStr != "false" && jsonStr != "False")) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {bl}"); + if (bl && (stringValue != "1" && stringValue != "true" && stringValue != "True")) + throw new Exception($"{method}: {property} not equal: {stringValue} vs {bl}"); + if (!bl && (stringValue != "0" && stringValue != "-1" && stringValue != "false" && stringValue != "False")) + throw new Exception($"{method}: {property} not equal: {stringValue} vs {bl}"); } else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true) { // TODO enum comparing } - else if (!jsonValue.Value()!.Equals(Convert.ToString(objectValue, CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)) + else if (!stringValue!.Equals(Convert.ToString(objectValue, CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)) { - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {objectValue}"); + throw new Exception($"{method}: {property} not equal: {stringValue} vs {objectValue}"); } } - else if (jsonValue.Type == JTokenType.Integer) + else if (jsonValue.ValueKind == JsonValueKind.Number) { + var value = jsonValue.GetDecimal(); if (objectValue is DateTime time) { - if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value()!)) - throw new Exception($"{method}: {property} not equal: {DateTimeConverter.ParseFromDouble(jsonValue.Value()!)} vs {time}"); + if (time != DateTimeConverter.ParseFromDouble((double)value)) + throw new Exception($"{method}: {property} not equal: {DateTimeConverter.ParseFromDouble((double)value!)} vs {time}"); } else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true) { // TODO enum comparing } - else if (jsonValue.Value() != Convert.ToInt64(objectValue)) + else if (value != Convert.ToInt64(objectValue)) { - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {Convert.ToInt64(objectValue)}"); + throw new Exception($"{method}: {property} not equal: {value} vs {Convert.ToInt64(objectValue)}"); } } - else if (jsonValue.Type == JTokenType.Boolean) + else if (jsonValue.ValueKind == JsonValueKind.True || jsonValue.ValueKind == JsonValueKind.False) { - if (jsonValue.Value() != (bool)objectValue) - throw new Exception($"{method}: {property} not equal: {jsonValue.Value()} vs {(bool)objectValue}"); + if (jsonValue.GetBoolean() != (bool)objectValue) + throw new Exception($"{method}: {property} not equal: {jsonValue.GetBoolean()} vs {(bool)objectValue}"); } } } diff --git a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs index bec8b8d..dde591d 100644 --- a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs +++ b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs @@ -1,10 +1,10 @@ using System; using System.Net.WebSockets; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; -using Newtonsoft.Json; namespace CryptoExchange.Net.Testing.Implementations { @@ -87,7 +87,7 @@ namespace CryptoExchange.Net.Testing.Implementations public void InvokeMessage(T data) { - OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)))).Wait(); + OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data)))).Wait(); } public Task ReconnectAsync() => throw new NotImplementedException(); diff --git a/CryptoExchange.Net/Testing/RestRequestValidator.cs b/CryptoExchange.Net/Testing/RestRequestValidator.cs index 701bb54..a02a4d1 100644 --- a/CryptoExchange.Net/Testing/RestRequestValidator.cs +++ b/CryptoExchange.Net/Testing/RestRequestValidator.cs @@ -22,7 +22,6 @@ namespace CryptoExchange.Net.Testing private readonly string _folder; private readonly string _baseAddress; private readonly string? _nestedPropertyForCompare; - private readonly bool _stjCompare; /// /// ctor @@ -32,15 +31,13 @@ namespace CryptoExchange.Net.Testing /// The base address that is expected /// Func for checking if the request is authenticated /// Property to use for compare - /// Use System.Text.Json for comparing - public RestRequestValidator(TClient client, string folder, string baseAddress, Func isAuthenticated, string? nestedPropertyForCompare = null, bool stjCompare = true) + public RestRequestValidator(TClient client, string folder, string baseAddress, Func isAuthenticated, string? nestedPropertyForCompare = null) { _client = client; _folder = folder; _baseAddress = baseAddress; _nestedPropertyForCompare = nestedPropertyForCompare; _isAuthenticated = isAuthenticated; - _stjCompare = stjCompare; } /// @@ -127,10 +124,7 @@ namespace CryptoExchange.Net.Testing { // Check response data object responseData = (TActualResponse)result.Data!; - if (_stjCompare == true) - SystemTextJsonComparer.CompareData(name, responseData, response, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useSingleArrayItem); - else - JsonNetComparer.CompareData(name, responseData, response, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useSingleArrayItem); + SystemTextJsonComparer.CompareData(name, responseData, response, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useSingleArrayItem); } Trace.Listeners.Remove(listener); diff --git a/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs b/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs index 87ca865..b856a6c 100644 --- a/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs +++ b/CryptoExchange.Net/Testing/SocketSubscriptionValidator.cs @@ -2,12 +2,12 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Testing.Comparers; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -23,7 +23,6 @@ namespace CryptoExchange.Net.Testing private readonly string _folder; private readonly string _baseAddress; private readonly string? _nestedPropertyForCompare; - private readonly bool _stjCompare; /// /// ctor @@ -32,14 +31,12 @@ namespace CryptoExchange.Net.Testing /// Folder for json test values /// The base address that is expected /// Property to use for compare - /// Use System.Text.Json for comparing - public SocketSubscriptionValidator(TClient client, string folder, string baseAddress, string? nestedPropertyForCompare = null, bool stjCompare = true) + public SocketSubscriptionValidator(TClient client, string folder, string baseAddress, string? nestedPropertyForCompare = null) { _client = client; _folder = folder; _baseAddress = baseAddress; _nestedPropertyForCompare = nestedPropertyForCompare; - _stjCompare = stjCompare; } /// @@ -113,31 +110,31 @@ namespace CryptoExchange.Net.Testing if (lastMessage == null) throw new Exception($"{name} expected to {line} to be send to server but did not receive anything"); - var lastMessageJson = JToken.Parse(lastMessage); - var expectedJson = JToken.Parse(line.Substring(2)); - foreach(var item in expectedJson) + var lastMessageJson = JsonDocument.Parse(lastMessage).RootElement; + var expectedJson = JsonDocument.Parse(line.Substring(2)).RootElement; + if (expectedJson.ValueKind == JsonValueKind.Object) { - if (item is JProperty prop && prop.Value is JValue val) + foreach (var item in expectedJson.EnumerateObject()) { - if (val.ToString().StartsWith("|") && val.ToString().EndsWith("|")) + if (item.Value.ToString().StartsWith("|") && item.Value.ToString().EndsWith("|")) { // |x| values are used to replace parts or response messages - overrideKey = val.ToString(); - overrideValue = lastMessageJson[prop.Name]?.Value(); + overrideKey = item.Value.ToString(); + overrideValue = lastMessageJson.GetProperty(item.Name).GetString(); } - else if (val.ToString() == "-999") + else if (item.Value.ToString() == "-999") { // -999 value is used to replace parts or response messages - overrideKey = val.ToString(); - overrideValue = lastMessageJson[prop.Name]?.Value().ToString(); + overrideKey = item.Value.ToString(); + overrideValue = lastMessageJson.GetProperty(item.Name).GetDecimal().ToString(); } - else if (lastMessageJson[prop.Name]?.Value() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true) + else if (lastMessageJson.GetProperty(item.Name).GetString() != item.Value.ToString() && ignoreProperties?.Contains(item.Name) != true) { - throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value()}"); + throw new Exception($"{name} Expected {item.Name} to be {item.Value}, but was {lastMessageJson.GetProperty(item.Name).GetString()}"); } } + // TODO check arrays and sub-objects - // TODO check objects and arrays } } else if (line.StartsWith("< ")) @@ -161,10 +158,7 @@ namespace CryptoExchange.Net.Testing if (update == null) throw new Exception($"{name} Update send to client did not trigger in update handler"); - if (_stjCompare == true) - SystemTextJsonComparer.CompareData(name, update, compareData, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useFirstUpdateItem ?? false); - else - JsonNetComparer.CompareData(name, update, compareData, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useFirstUpdateItem ?? false); + SystemTextJsonComparer.CompareData(name, update, compareData, nestedJsonProperty ?? _nestedPropertyForCompare, ignoreProperties, useFirstUpdateItem ?? false); } }