mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 07:56:12 +00:00
Removed Newtonsoft.Json dependency
This commit is contained in:
parent
0d4ab96e19
commit
e138d7d263
@ -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<TimeObject>($"{{ \"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<TimeObject>($"{{ \"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<TimeObject>($"{{ \"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<TimeObject>($"{{ \"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<EnumObject>($"{{ \"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<NotNullableEnumObject>($"{{ \"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<BoolObject>($"{{ \"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<NotNullableBoolObject>($"{{ \"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<TestEnum>))]
|
||||
public enum TestEnum
|
||||
{
|
||||
[Map("1")]
|
||||
One,
|
||||
[Map("2")]
|
||||
Two,
|
||||
[Map("three", "3")]
|
||||
Three,
|
||||
Four
|
||||
}
|
||||
}
|
@ -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<TestObject>().Result;
|
||||
@ -140,7 +136,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
|
||||
client.SetResponse("{}", out var request);
|
||||
|
||||
await client.Api1.RequestWithParams<TestObject>(new HttpMethod(method), new Dictionary<string, object>
|
||||
await client.Api1.RequestWithParams<TestObject>(new HttpMethod(method), new ParameterCollection
|
||||
{
|
||||
{ "TestParam1", "Value1" },
|
||||
{ "TestParam2", 2 },
|
||||
|
@ -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
|
||||
|
@ -363,6 +363,18 @@ namespace CryptoExchange.Net.UnitTests
|
||||
public string Prop32 { get; set; }
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(EnumConverter<TestEnum>))]
|
||||
public enum TestEnum
|
||||
{
|
||||
[Map("1")]
|
||||
One,
|
||||
[Map("2")]
|
||||
Two,
|
||||
[Map("three", "3")]
|
||||
Three,
|
||||
Four
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(Test))]
|
||||
[JsonSerializable(typeof(Test2))]
|
||||
[JsonSerializable(typeof(Test3))]
|
||||
|
@ -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!;
|
||||
}
|
||||
|
||||
|
@ -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<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
/// <inheritdoc />
|
||||
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<CallResult<T>> Request<T>(CancellationToken ct = default) where T : class
|
||||
{
|
||||
return await SendRequestAsync<T>(new Uri("http://www.test.com"), HttpMethod.Get, ct, requestWeight: 0);
|
||||
return await SendAsync<T>("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct);
|
||||
}
|
||||
|
||||
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
|
||||
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, ParameterCollection parameters, Dictionary<string, string> headers) where T : class
|
||||
{
|
||||
return await SendRequestAsync<T>(new Uri("http://www.test.com"), method, default, parameters, requestWeight: 0, additionalHeaders: headers);
|
||||
return await SendAsync<T>("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<IRequestFactory>().Object;
|
||||
}
|
||||
|
||||
protected override IStreamMessageAccessor CreateAccessor() => new SystemTextJsonStreamMessageAccessor(new System.Text.Json.JsonSerializerOptions());
|
||||
protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(new TestSerializerContext());
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}";
|
||||
|
||||
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T : class
|
||||
{
|
||||
return await SendRequestAsync<T>(new Uri("http://www.test.com"), HttpMethod.Get, ct, requestWeight: 0);
|
||||
return await SendAsync<T>("http://www.test.com", new RequestDefinition("/", HttpMethod.Get) { Weight = 0 }, null, ct);
|
||||
}
|
||||
|
||||
protected override Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] 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; }
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode futuresType, DateTime? deliverDate = null) => $"{baseAsset.ToUpperInvariant()}{quoteAsset.ToUpperInvariant()}";
|
||||
|
||||
|
21
CryptoExchange.Net.UnitTests/TestSerializerContext.cs
Normal file
21
CryptoExchange.Net.UnitTests/TestSerializerContext.cs
Normal file
@ -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<string, string>))]
|
||||
[JsonSerializable(typeof(IDictionary<string, string>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, object>))]
|
||||
[JsonSerializable(typeof(IDictionary<string, object>))]
|
||||
[JsonSerializable(typeof(TestObject))]
|
||||
internal partial class TestSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@ -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
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IStreamMessageAccessor CreateAccessor() => new JsonNetStreamMessageAccessor();
|
||||
protected abstract IStreamMessageAccessor CreateAccessor();
|
||||
|
||||
/// <summary>
|
||||
/// Create a serializer instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IMessageSerializer CreateSerializer() => new JsonNetMessageSerializer();
|
||||
protected abstract IMessageSerializer CreateSerializer();
|
||||
|
||||
/// <summary>
|
||||
/// Send a request to the base address based on the request definition
|
||||
|
@ -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
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal virtual IByteMessageAccessor CreateAccessor() => new JsonNetByteMessageAccessor();
|
||||
protected internal abstract IByteMessageAccessor CreateAccessor();
|
||||
|
||||
/// <summary>
|
||||
/// Create a serializer instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal virtual IMessageSerializer CreateSerializer() => new JsonNetMessageSerializer();
|
||||
protected internal abstract IMessageSerializer CreateSerializer();
|
||||
|
||||
/// <summary>
|
||||
/// Keep an open connection to this url
|
||||
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<ArrayPropertyAttribute>(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<JsonConverterAttribute>(property) ?? GetCustomAttribute<JsonConverterAttribute>(property.PropertyType);
|
||||
var conversionAttribute = GetCustomAttribute<JsonConversionAttribute>(property) ?? GetCustomAttribute<JsonConversionAttribute>(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<decimal>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<ArrayPropertyAttribute>(p)?.Index);
|
||||
|
||||
var last = -1;
|
||||
foreach (var prop in ordered)
|
||||
{
|
||||
var arrayProp = GetCustomAttribute<ArrayPropertyAttribute>(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<JsonConverterAttribute>(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<T>(MemberInfo memberInfo) where T : Attribute =>
|
||||
(T?)_attributeByMemberInfoAndTypeCache.GetOrAdd((memberInfo, typeof(T)), tuple => memberInfo.GetCustomAttribute(typeof(T))!);
|
||||
|
||||
private static T? GetCustomAttribute<T>(Type type) where T : Attribute =>
|
||||
(T?)_attributeByTypeAndTypeCache.GetOrAdd((type, typeof(T)), tuple => type.GetCustomAttribute(typeof(T))!);
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for enum converters
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of enum to convert</typeparam>
|
||||
public abstract class BaseConverter<T>: JsonConverter where T: struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The enum->string mapping
|
||||
/// </summary>
|
||||
protected abstract List<KeyValuePair<T, string>> Mapping { get; }
|
||||
private readonly bool _quotes;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="useQuotes"></param>
|
||||
protected BaseConverter(bool useQuotes)
|
||||
{
|
||||
_quotes = useQuotes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a string value
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public T ReadString(string data)
|
||||
{
|
||||
return Mapping.FirstOrDefault(v => v.Value == data).Key;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
// Check if it is type, or nullable of type
|
||||
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
|
||||
}
|
||||
|
||||
private bool GetValue(string value, out T result)
|
||||
{
|
||||
// Check for exact match first, then if not found fallback to a case insensitive match
|
||||
var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
|
||||
if(mapping.Equals(default(KeyValuePair<T, string>)))
|
||||
mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (!mapping.Equals(default(KeyValuePair<T, string>)))
|
||||
{
|
||||
result = mapping.Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetValue(T value)
|
||||
{
|
||||
return Mapping.FirstOrDefault(v => v.Key.Equals(value)).Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Decimal converter that handles overflowing decimal values (by setting it to decimal.MaxValue)
|
||||
/// </summary>
|
||||
public class BigDecimalConverter : JsonConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
if (Nullable.GetUnderlyingType(objectType) != null)
|
||||
return Nullable.GetUnderlyingType(objectType) == typeof(decimal);
|
||||
return objectType == typeof(decimal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean converter with support for "0"/"1" (strings)
|
||||
/// </summary>
|
||||
public class BoolConverter : JsonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
if (Nullable.GetUnderlyingType(objectType) != null)
|
||||
return Nullable.GetUnderlyingType(objectType) == typeof(bool);
|
||||
return objectType == typeof(bool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <param name="existingValue">The existing value of object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
/// <returns>
|
||||
/// The object value.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that this converter will not participate in writing results.
|
||||
/// </summary>
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Datetime converter. Supports converting from string/long/double to DateTime and back. Numbers are assumed to be the time since 1970-01-01.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a long value to datetime
|
||||
/// </summary>
|
||||
/// <param name="longValue"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a string value to datetime
|
||||
/// </summary>
|
||||
/// <param name="stringValue"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a seconds since epoch (01-01-1970) value to DateTime
|
||||
/// </summary>
|
||||
/// <param name="seconds"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromSeconds(double seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond));
|
||||
/// <summary>
|
||||
/// Convert a milliseconds since epoch (01-01-1970) value to DateTime
|
||||
/// </summary>
|
||||
/// <param name="milliseconds"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromMilliseconds(double milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond));
|
||||
/// <summary>
|
||||
/// Convert a microseconds since epoch (01-01-1970) value to DateTime
|
||||
/// </summary>
|
||||
/// <param name="microseconds"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromMicroseconds(long microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond));
|
||||
/// <summary>
|
||||
/// Convert a nanoseconds since epoch (01-01-1970) value to DateTime
|
||||
/// </summary>
|
||||
/// <param name="nanoseconds"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromNanoseconds(long nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond));
|
||||
/// <summary>
|
||||
/// Convert a DateTime value to seconds since epoch (01-01-1970) value
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("time")]
|
||||
public static long? ConvertToSeconds(DateTime? time) => time == null ? null: (long)Math.Round((time.Value - _epoch).TotalSeconds);
|
||||
/// <summary>
|
||||
/// Convert a DateTime value to milliseconds since epoch (01-01-1970) value
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("time")]
|
||||
public static long? ConvertToMilliseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalMilliseconds);
|
||||
/// <summary>
|
||||
/// Convert a DateTime value to microseconds since epoch (01-01-1970) value
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("time")]
|
||||
public static long? ConvertToMicroseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerMicrosecond);
|
||||
/// <summary>
|
||||
/// Convert a DateTime value to nanoseconds since epoch (01-01-1970) value
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("time")]
|
||||
public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Converter for serializing decimal values as string
|
||||
/// </summary>
|
||||
public class DecimalStringWriterConverter : JsonConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType) => objectType == typeof(decimal) || objectType == typeof(decimal?);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => writer.WriteValue(((decimal?)value)?.ToString(CultureInfo.InvariantCulture) ?? 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Converter for enum values. Enums entries should be noted with a MapAttribute to map the enum value to a string value
|
||||
/// </summary>
|
||||
public class EnumConverter : JsonConverter
|
||||
{
|
||||
private bool _warnOnMissingEntry = true;
|
||||
private bool _writeAsInt;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public EnumConverter() { }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="writeAsInt"></param>
|
||||
/// <param name="warnOnMissingEntry"></param>
|
||||
public EnumConverter(bool writeAsInt, bool warnOnMissingEntry)
|
||||
{
|
||||
_writeAsInt = writeAsInt;
|
||||
_warnOnMissingEntry = warnOnMissingEntry;
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, List<KeyValuePair<object, string>>> _mapping = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType.IsEnum || Nullable.GetUnderlyingType(objectType)?.IsEnum == true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<KeyValuePair<object, string>> AddMapping(Type objectType)
|
||||
{
|
||||
var mapping = new List<KeyValuePair<object, string>>();
|
||||
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<object, string>(Enum.Parse(objectType, member.Name), value));
|
||||
}
|
||||
}
|
||||
_mapping.TryAdd(objectType, mapping);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private static bool GetValue(Type objectType, List<KeyValuePair<object, string>> 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<object, string>)))
|
||||
mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (!mapping.Equals(default(KeyValuePair<object, string>)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="enumValue"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("enumValue")]
|
||||
public static string? GetString<T>(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());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Json.Net message accessor
|
||||
/// </summary>
|
||||
public abstract class JsonNetMessageAccessor : IMessageAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// The json token loaded
|
||||
/// </summary>
|
||||
protected JToken? _token;
|
||||
private static readonly JsonSerializer _serializer = JsonSerializer.Create(SerializerOptions.WithConverters);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsJson { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool OriginalDataAvailable { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? Underlying => _token;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CallResult<object> Deserialize(Type type, MessagePath? path = null)
|
||||
{
|
||||
if (!IsJson)
|
||||
return new CallResult<object>(GetOriginalString());
|
||||
|
||||
var source = _token;
|
||||
if (path != null)
|
||||
source = GetPathNode(path.Value);
|
||||
|
||||
try
|
||||
{
|
||||
var result = source!.ToObject(type, _serializer)!;
|
||||
return new CallResult<object>(result);
|
||||
}
|
||||
catch (JsonReaderException jre)
|
||||
{
|
||||
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}";
|
||||
return new CallResult<object>(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<object>(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<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CallResult<T> Deserialize<T>(MessagePath? path = null)
|
||||
{
|
||||
var source = _token;
|
||||
if (path != null)
|
||||
source = GetPathNode(path.Value);
|
||||
|
||||
try
|
||||
{
|
||||
var result = source!.ToObject<T>(_serializer)!;
|
||||
return new CallResult<T>(result);
|
||||
}
|
||||
catch (JsonReaderException jre)
|
||||
{
|
||||
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}";
|
||||
return new CallResult<T>(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<T>(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<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T? GetValue<T>(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<T>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<T?>? GetValues<T>(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<T>().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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string GetOriginalString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json.Net stream message accessor
|
||||
/// </summary>
|
||||
public class JsonNetStreamMessageAccessor : JsonNetMessageAccessor, IStreamMessageAccessor
|
||||
{
|
||||
private Stream? _stream;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<CallResult> 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));
|
||||
}
|
||||
|
||||
}
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
_token = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json.Net byte message accessor
|
||||
/// </summary>
|
||||
public class JsonNetByteMessageAccessor : JsonNetMessageAccessor, IByteMessageAccessor
|
||||
{
|
||||
private ReadOnlyMemory<byte> _bytes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CallResult Read(ReadOnlyMemory<byte> 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetOriginalString() =>
|
||||
// Netstandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
|
||||
#if NETSTANDARD2_0
|
||||
Encoding.UTF8.GetString(_bytes.ToArray());
|
||||
#else
|
||||
Encoding.UTF8.GetString(_bytes.Span);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OriginalDataAvailable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_bytes = null;
|
||||
_token = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class JsonNetMessageSerializer : IMessageSerializer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Serialize<T>(T message) => JsonConvert.SerializeObject(message, Formatting.None);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CryptoExchange.Net.Converters.JsonNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializer options
|
||||
/// </summary>
|
||||
public static class SerializerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Json serializer settings which includes the EnumConverter, DateTimeConverter and BoolConverter
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings WithConverters => new JsonSerializerSettings
|
||||
{
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
Converters =
|
||||
{
|
||||
new EnumConverter(),
|
||||
new DateTimeConverter(),
|
||||
new BoolConverter()
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Default json serializer settings
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings Default => new JsonSerializerSettings
|
||||
{
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
Culture = CultureInfo.InvariantCulture
|
||||
};
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
@ -55,10 +55,9 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||
|
@ -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<string>? 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<ArrayPropertyAttribute>().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<ArrayPropertyAttribute>().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<string>? 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<JsonPropertyAttribute>(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<string>? 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<ArrayPropertyAttribute>().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<ArrayPropertyAttribute>().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<ArrayPropertyAttribute>().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<decimal>() != dec)
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {dec}");
|
||||
}
|
||||
else if (objectValue is DateTime time)
|
||||
{
|
||||
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
||||
}
|
||||
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
|
||||
{
|
||||
// TODO enum comparing
|
||||
}
|
||||
else if (!jsonValue.Value<string>()!.Equals(Convert.ToString(objectValue, CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {objectValue}");
|
||||
}
|
||||
}
|
||||
else if (jsonValue.Type == JTokenType.Integer)
|
||||
{
|
||||
if (objectValue is DateTime time)
|
||||
{
|
||||
if (time != DateTimeConverter.ParseFromLong(jsonValue.Value<long>()!))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
||||
}
|
||||
else if (propertyType.IsEnum)
|
||||
{
|
||||
// TODO enum comparing
|
||||
}
|
||||
else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue))
|
||||
{
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}");
|
||||
}
|
||||
}
|
||||
else if (jsonValue.Type == JTokenType.Boolean)
|
||||
{
|
||||
if (objectValue is bool boolVal && jsonValue.Value<bool>() != boolVal)
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<bool>()} vs {(bool)objectValue}");
|
||||
|
||||
if (jsonValue.Value<bool>() != bool.Parse(objectValue.ToString()!))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<bool>()} vs {(bool)objectValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ArrayPropertyAttribute>().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<string>? ignoreProperties)
|
||||
private static void CheckObject(string method, JsonProperty prop, object obj, List<string>? 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<string>? ignoreProperties = null)
|
||||
private static void CheckPropertyValue(string method, JsonElement propValue, object? propertyValue, Type propertyType, string? propertyName = null, string? propName = null, List<string>? 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<ArrayPropertyAttribute>().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<ArrayPropertyAttribute>().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<ArrayPropertyAttribute>().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<decimal>() != dec)
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} 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<string>()!;
|
||||
if (!string.IsNullOrEmpty(jsonStr) && time != DateTimeConverter.ParseFromString(jsonStr))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} 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<string>();
|
||||
if (bl && (jsonStr != "1" && jsonStr != "true" && jsonStr != "True"))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {bl}");
|
||||
if (!bl && (jsonStr != "0" && jsonStr != "-1" && jsonStr != "false" && jsonStr != "False"))
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} 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<string>()!.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<string>()} 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<long>()!))
|
||||
throw new Exception($"{method}: {property} not equal: {DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!)} 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<long>() != Convert.ToInt64(objectValue))
|
||||
else if (value != Convert.ToInt64(objectValue))
|
||||
{
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} 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>() != (bool)objectValue)
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<bool>()} vs {(bool)objectValue}");
|
||||
if (jsonValue.GetBoolean() != (bool)objectValue)
|
||||
throw new Exception($"{method}: {property} not equal: {jsonValue.GetBoolean()} vs {(bool)objectValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>(T data)
|
||||
{
|
||||
OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)))).Wait();
|
||||
OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data)))).Wait();
|
||||
}
|
||||
|
||||
public Task ReconnectAsync() => throw new NotImplementedException();
|
||||
|
@ -22,7 +22,6 @@ namespace CryptoExchange.Net.Testing
|
||||
private readonly string _folder;
|
||||
private readonly string _baseAddress;
|
||||
private readonly string? _nestedPropertyForCompare;
|
||||
private readonly bool _stjCompare;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
@ -32,15 +31,13 @@ namespace CryptoExchange.Net.Testing
|
||||
/// <param name="baseAddress">The base address that is expected</param>
|
||||
/// <param name="isAuthenticated">Func for checking if the request is authenticated</param>
|
||||
/// <param name="nestedPropertyForCompare">Property to use for compare</param>
|
||||
/// <param name="stjCompare">Use System.Text.Json for comparing</param>
|
||||
public RestRequestValidator(TClient client, string folder, string baseAddress, Func<WebCallResult, bool> isAuthenticated, string? nestedPropertyForCompare = null, bool stjCompare = true)
|
||||
public RestRequestValidator(TClient client, string folder, string baseAddress, Func<WebCallResult, bool> isAuthenticated, string? nestedPropertyForCompare = null)
|
||||
{
|
||||
_client = client;
|
||||
_folder = folder;
|
||||
_baseAddress = baseAddress;
|
||||
_nestedPropertyForCompare = nestedPropertyForCompare;
|
||||
_isAuthenticated = isAuthenticated;
|
||||
_stjCompare = stjCompare;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
@ -32,14 +31,12 @@ namespace CryptoExchange.Net.Testing
|
||||
/// <param name="folder">Folder for json test values</param>
|
||||
/// <param name="baseAddress">The base address that is expected</param>
|
||||
/// <param name="nestedPropertyForCompare">Property to use for compare</param>
|
||||
/// <param name="stjCompare">Use System.Text.Json for comparing</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<string>();
|
||||
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<decimal>().ToString();
|
||||
overrideKey = item.Value.ToString();
|
||||
overrideValue = lastMessageJson.GetProperty(item.Name).GetDecimal().ToString();
|
||||
}
|
||||
else if (lastMessageJson[prop.Name]?.Value<string>() != 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<string>()}");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user