mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-07-06 14:16:36 +00:00
commit 90f285d7f6bcd926ce9ca3d5832b1d70a5eae6ab Author: JKorf <jankorf91@gmail.com> Date: Sun Jun 25 19:51:12 2023 +0200 Docs commit 72187035c703d1402b37bd2f4c3e066706f28d67 Author: JKorf <jankorf91@gmail.com> Date: Sat Jun 24 16:02:53 2023 +0200 docs commit 8411977292f1fb0b6e0705b1ad675b79a5311d90 Author: JKorf <jankorf91@gmail.com> Date: Fri Jun 23 18:25:15 2023 +0200 wip commit cb7d33aad5d2751104c8b8a6c6eadbf0d36b672c Author: JKorf <jankorf91@gmail.com> Date: Fri Jun 2 19:26:26 2023 +0200 wip commit 4359a2d05ea1141cff516dab18f364a6ca854e18 Author: JKorf <jankorf91@gmail.com> Date: Wed May 31 20:51:36 2023 +0200 wip commit c6adb1b2f728d143f6bd667139c619581122a3c9 Author: JKorf <jankorf91@gmail.com> Date: Mon May 1 21:13:47 2023 +0200 wip commit 7fee733f82fa6ff574030452f0955c9e817647dd Author: JKorf <jankorf91@gmail.com> Date: Thu Apr 27 13:02:56 2023 +0200 wip commit f8057313ffc9b0c31effcda71d35d105ea390971 Author: JKorf <jankorf91@gmail.com> Date: Mon Apr 17 21:37:51 2023 +0200 wip
209 lines
8.4 KiB
C#
209 lines
8.4 KiB
C#
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
|
|
{
|
|
/// <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 ((property.PropertyType == typeof(decimal)
|
|
|| property.PropertyType == typeof(decimal?))
|
|
&& (value != null && value.ToString().IndexOf("e", StringComparison.OrdinalIgnoreCase) >= 0))
|
|
{
|
|
if (decimal.TryParse(value.ToString(), 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)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mark property as an index in the array
|
|
/// </summary>
|
|
[AttributeUsage(AttributeTargets.Property)]
|
|
public class ArrayPropertyAttribute: Attribute
|
|
{
|
|
/// <summary>
|
|
/// The index in the array
|
|
/// </summary>
|
|
public int Index { get; }
|
|
|
|
/// <summary>
|
|
/// ctor
|
|
/// </summary>
|
|
/// <param name="index"></param>
|
|
public ArrayPropertyAttribute(int index)
|
|
{
|
|
Index = index;
|
|
}
|
|
}
|
|
}
|