From 28e2ed10f24bf9917c66be8a8c1e5ededec30eb4 Mon Sep 17 00:00:00 2001 From: Oleg Rakhmatulin Date: Wed, 28 Feb 2024 12:06:51 +0100 Subject: [PATCH] Issue #728 - JSON string-to-enum converters improvements: - Added internal helper class for creating/storing string name (from attribute) to value mapping for enums. - Added internal helper extension method for converting a string into an enum value with fallback handling. - The new helper method is used for all custom JSON-to-enum converters now. (cherry picked from commit 00d8a6eefe6045997df8e199b9ba46770bf875f4) --- .../Helpers/AssetAttributesEnumConverter.cs | 4 +-- .../Helpers/CryptoExchangeEnumConverter.cs | 4 +-- .../Helpers/DictionaryExtensions.cs | 7 ++++ Alpaca.Markets/Helpers/EnumExtensions.cs | 33 ++++++++++++++++++- .../Helpers/ExchangeEnumConverter.cs | 4 +-- .../Helpers/OrderSideEnumConverter.cs | 4 +-- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Alpaca.Markets/Helpers/AssetAttributesEnumConverter.cs b/Alpaca.Markets/Helpers/AssetAttributesEnumConverter.cs index 40a55bf5..0964b635 100644 --- a/Alpaca.Markets/Helpers/AssetAttributesEnumConverter.cs +++ b/Alpaca.Markets/Helpers/AssetAttributesEnumConverter.cs @@ -5,7 +5,7 @@ Justification = "Object instances of this class will be created by Newtonsoft.JSON library.")] internal sealed class AssetAttributesEnumConverter : StringEnumConverter { - public override Object? ReadJson( + public override Object ReadJson( JsonReader reader, Type objectType, Object? existingValue, @@ -13,7 +13,7 @@ internal sealed class AssetAttributesEnumConverter : StringEnumConverter { try { - return base.ReadJson(reader, objectType, existingValue, serializer); + return AssetAttributes.Unknown.FromEnumString(reader); } catch (JsonSerializationException) { diff --git a/Alpaca.Markets/Helpers/CryptoExchangeEnumConverter.cs b/Alpaca.Markets/Helpers/CryptoExchangeEnumConverter.cs index 2da65940..be037b49 100644 --- a/Alpaca.Markets/Helpers/CryptoExchangeEnumConverter.cs +++ b/Alpaca.Markets/Helpers/CryptoExchangeEnumConverter.cs @@ -5,7 +5,7 @@ Justification = "Object instances of this class will be created by Newtonsoft.JSON library.")] internal sealed class CryptoExchangeEnumConverter : StringEnumConverter { - public override Object? ReadJson( + public override Object ReadJson( JsonReader reader, Type objectType, Object? existingValue, @@ -13,7 +13,7 @@ internal sealed class CryptoExchangeEnumConverter : StringEnumConverter { try { - return base.ReadJson(reader, objectType, existingValue, serializer); + return CryptoExchange.Unknown.FromEnumString(reader); } catch (JsonSerializationException) { diff --git a/Alpaca.Markets/Helpers/DictionaryExtensions.cs b/Alpaca.Markets/Helpers/DictionaryExtensions.cs index 5602518c..cb252325 100644 --- a/Alpaca.Markets/Helpers/DictionaryExtensions.cs +++ b/Alpaca.Markets/Helpers/DictionaryExtensions.cs @@ -10,4 +10,11 @@ public static IReadOnlyDictionary> SetSymbol pair.Value.SetSymbol(pair.Key).EmptyIfNull(), StringComparer.Ordinal) ?? new Dictionary>(StringComparer.Ordinal); + +#if NETFRAMEWORK || NETSTANDARD2_0 + public static TValue GetValueOrDefault( + this IReadOnlyDictionary dictionary, + TKey key, TValue defaultValue) => + dictionary.TryGetValue(key, out var value) ? value : defaultValue; +#endif } diff --git a/Alpaca.Markets/Helpers/EnumExtensions.cs b/Alpaca.Markets/Helpers/EnumExtensions.cs index b36fc1fb..2c95ccf3 100644 --- a/Alpaca.Markets/Helpers/EnumExtensions.cs +++ b/Alpaca.Markets/Helpers/EnumExtensions.cs @@ -1,11 +1,42 @@ -namespace Alpaca.Markets; +using System.Reflection; + +namespace Alpaca.Markets; internal static class EnumExtensions { + private static class NamesHelper< +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] +#endif + T> where T : struct, Enum + { + public static readonly IReadOnlyDictionary ValuesByNames = + Enum.GetValues(typeof(T)).OfType() + .ToDictionary(getJsonName, value => value); + + private static string getJsonName( + T value) => + typeof(T).GetField(Enum.GetName(typeof(T), value) ?? value.ToString())? + .GetCustomAttribute()?.Value ?? value.ToString(); + } + private static readonly Char[] _doubleQuotes = [ '"' ]; public static String ToEnumString( this T enumValue) where T : struct, Enum => JsonConvert.SerializeObject(enumValue).Trim(_doubleQuotes); + + public static T FromEnumString< +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] +#endif + T>( + this T fallbackEnumValue, + JsonReader reader) + where T : struct, Enum => + reader.TokenType == JsonToken.String + ? NamesHelper.ValuesByNames.GetValueOrDefault( + reader.Value as String ?? String.Empty, fallbackEnumValue) + : fallbackEnumValue; } diff --git a/Alpaca.Markets/Helpers/ExchangeEnumConverter.cs b/Alpaca.Markets/Helpers/ExchangeEnumConverter.cs index 738bdc05..bd7155da 100644 --- a/Alpaca.Markets/Helpers/ExchangeEnumConverter.cs +++ b/Alpaca.Markets/Helpers/ExchangeEnumConverter.cs @@ -5,7 +5,7 @@ Justification = "Object instances of this class will be created by Newtonsoft.JSON library.")] internal sealed class ExchangeEnumConverter : StringEnumConverter { - public override Object? ReadJson( + public override Object ReadJson( JsonReader reader, Type objectType, Object? existingValue, @@ -13,7 +13,7 @@ internal sealed class ExchangeEnumConverter : StringEnumConverter { try { - return base.ReadJson(reader, objectType, existingValue, serializer); + return Exchange.Unknown.FromEnumString(reader); } catch (JsonSerializationException) { diff --git a/Alpaca.Markets/Helpers/OrderSideEnumConverter.cs b/Alpaca.Markets/Helpers/OrderSideEnumConverter.cs index 768e92a6..0574ac5c 100644 --- a/Alpaca.Markets/Helpers/OrderSideEnumConverter.cs +++ b/Alpaca.Markets/Helpers/OrderSideEnumConverter.cs @@ -5,7 +5,7 @@ Justification = "Object instances of this class will be created by Newtonsoft.JSON library.")] internal sealed class OrderSideEnumConverter : StringEnumConverter { - public override Object? ReadJson( + public override Object ReadJson( JsonReader reader, Type objectType, Object? existingValue, @@ -13,7 +13,7 @@ internal sealed class OrderSideEnumConverter : StringEnumConverter { try { - return base.ReadJson(reader, objectType, existingValue, serializer); + return OrderSide.Sell.FromEnumString(reader); } catch (JsonSerializationException) {