Skip to content

Commit

Permalink
Issue #728 - JSON string-to-enum converters improvements:
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
OlegRa committed Mar 1, 2024
1 parent b24f8c0 commit e995c24
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 10 deletions.
10 changes: 9 additions & 1 deletion Alpaca.Markets.Tests/AlpacaTradingClientTest.Assets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ namespace Alpaca.Markets.Tests;

public sealed partial class AlpacaTradingClientTest
{
private static readonly Char[] _doubleQuotes = [ '"' ];

private static String toEnumString<T>(
T enumValue)
where T : struct, Enum =>
JsonConvert.SerializeObject(enumValue).Trim(_doubleQuotes);

[Fact]
public async Task GetAssetAsyncWorks()
{
Expand Down Expand Up @@ -42,7 +49,8 @@ private static JObject createAsset(
Guid assetId,
String symbol) =>
new(
new JProperty("attributes", new JArray(AssetAttributes.PtpNoException)),
new JProperty("attributes", new JArray(
toEnumString(AssetAttributes.PtpNoException))),
new JProperty("maintenance_margin_requirement", 100),
new JProperty("status", AssetStatus.Active),
new JProperty("class", AssetClass.UsEquity),
Expand Down
4 changes: 2 additions & 2 deletions Alpaca.Markets/Helpers/AssetAttributesEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
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,
JsonSerializer serializer)
{
try
{
return base.ReadJson(reader, objectType, existingValue, serializer);
return AssetAttributes.Unknown.FromEnumString(reader);
}
catch (JsonSerializationException)
{
Expand Down
4 changes: 2 additions & 2 deletions Alpaca.Markets/Helpers/CryptoExchangeEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
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,
JsonSerializer serializer)
{
try
{
return base.ReadJson(reader, objectType, existingValue, serializer);
return CryptoExchange.Unknown.FromEnumString(reader);
}
catch (JsonSerializationException)
{
Expand Down
7 changes: 7 additions & 0 deletions Alpaca.Markets/Helpers/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ public static IReadOnlyDictionary<String, IReadOnlyList<TInto>> SetSymbol<TInto,
pair => pair.Value.SetSymbol(pair.Key).EmptyIfNull<TInto, TFrom>(),
StringComparer.Ordinal)
?? new Dictionary<String, IReadOnlyList<TInto>>(StringComparer.Ordinal);

#if NETFRAMEWORK || NETSTANDARD2_0
public static TValue GetValueOrDefault<TKey, TValue>(
this IReadOnlyDictionary<TKey, TValue> dictionary,
TKey key, TValue defaultValue) =>
dictionary.TryGetValue(key, out var value) ? value : defaultValue;
#endif
}
33 changes: 32 additions & 1 deletion Alpaca.Markets/Helpers/EnumExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<String, T> ValuesByNames =
Enum.GetValues(typeof(T)).OfType<T>()
.ToDictionary(getJsonName, value => value);

private static string getJsonName(
T value) =>
typeof(T).GetField(Enum.GetName(typeof(T), value) ?? value.ToString())?
.GetCustomAttribute<EnumMemberAttribute>()?.Value ?? value.ToString();
}

private static readonly Char[] _doubleQuotes = [ '"' ];

public static String ToEnumString<T>(
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<T>.ValuesByNames.GetValueOrDefault(
reader.Value as String ?? String.Empty, fallbackEnumValue)
: fallbackEnumValue;
}
4 changes: 2 additions & 2 deletions Alpaca.Markets/Helpers/ExchangeEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
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,
JsonSerializer serializer)
{
try
{
return base.ReadJson(reader, objectType, existingValue, serializer);
return Exchange.Unknown.FromEnumString(reader);
}
catch (JsonSerializationException)
{
Expand Down
4 changes: 2 additions & 2 deletions Alpaca.Markets/Helpers/OrderSideEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
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,
JsonSerializer serializer)
{
try
{
return base.ReadJson(reader, objectType, existingValue, serializer);
return OrderSide.Sell.FromEnumString(reader);
}
catch (JsonSerializationException)
{
Expand Down

0 comments on commit e995c24

Please sign in to comment.