Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #8 - Allow CurrencyExchange section to be either a single item or an array, and add element for InstructedAmount #9

Merged
merged 3 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,48 @@ namespace RobinTTY.NordigenApiClient.Tests.Serialization;
internal class TransactionTests
{
/// <summary>
/// Tests the correct deserialization of transactions.
/// Tests the correct deserialization of transactions with a single embedded currency exchange object.
/// </summary>
[Test]
public void DeserializeTransaction()
public void DeserializeTransactionWithSingleCurrencyExchange()
{
const string json =
"{ \"transactionId\": \"AB123456789\", \"entryReference\": \"123456789\", \"bookingDate\": \"2023-03-20\", \"bookingDateTime\": \"2023-03-20T00:00:00+00:00\", \"transactionAmount\": { \"amount\": \"-33.06\", \"currency\": \"GBP\" }, \"currencyExchange\": { \"sourceCurrency\": \"USD\", \"exchangeRate\": \"1.20961887\", \"unitCurrency\": \"USD\", \"targetCurrency\": \"GBP\", \"instructedAmount\": { \"amount\": \"-33.06\", \"currency\": \"GBP\" } }, \"remittanceInformationUnstructured\": \"my reference here\", \"additionalInformation\": \"123456789\", \"proprietaryBankTransactionCode\": \"OTHER_PURCHASE\", \"merchantCategoryCode\": \"5045\", \"internalTransactionId\": \"abcdef\" }";

var options = new JsonSerializerOptions
{
Converters = {new CultureSpecificDecimalConverter()}
};
var transaction = JsonSerializer.Deserialize<Transaction>(json, options);

Assert.Multiple(() =>
{
Assert.That(transaction!.CurrencyExchange, Is.Not.Null);
Assert.That(transaction.CurrencyExchange!.First().ExchangeRate, Is.EqualTo(1.20961887));
Assert.That(transaction.CurrencyExchange!.First().InstructedAmount!.Amount, Is.EqualTo(-33.06));
Assert.That(transaction.CurrencyExchange!.First().InstructedAmount!.Currency, Is.EqualTo("GBP"));
Assert.That(transaction.CurrencyExchange!.First().SourceCurrency, Is.EqualTo("USD"));
Assert.That(transaction.CurrencyExchange!.First().TargetCurrency, Is.EqualTo("GBP"));
Assert.That(transaction.CurrencyExchange!.First().UnitCurrency, Is.EqualTo("USD"));
Assert.That(transaction.CurrencyExchange!.First().QuotationDate, Is.Null);
});
}

/// <summary>
/// Tests the correct deserialization of transactions with multiple embedded currency exchange objects.
/// </summary>
[Test]
public void DeserializeTransactionWithMultipleCurrencyExchange()
{
const string json =
"{ \"transactionId\": \"AB123456789\", \"entryReference\": \"123456789\", \"bookingDate\": \"2023-03-20\", \"bookingDateTime\": \"2023-03-20T00:00:00+00:00\", \"transactionAmount\": { \"amount\": \"-33.06\", \"currency\": \"GBP\" }, \"currencyExchange\":[{\"sourceCurrency\":\"USD\",\"exchangeRate\":\"1.20961887\",\"unitCurrency\":\"USD\",\"targetCurrency\":\"GBP\"}], \"remittanceInformationUnstructured\": \"my reference here\", \"additionalInformation\": \"123456789\", \"proprietaryBankTransactionCode\": \"OTHER_PURCHASE\", \"merchantCategoryCode\": \"5045\", \"internalTransactionId\": \"abcdef\" }";

// We need the culture specific decimal converter here, since it it accepting strings
var options = new JsonSerializerOptions
{
Converters = {new JsonWebTokenConverter(), new GuidConverter(), new CultureSpecificDecimalConverter()}
Converters = { new CultureSpecificDecimalConverter() }
};
var transaction = JsonSerializer.Deserialize<Transaction>(json, options);

Assert.Multiple(() =>
{
Assert.That(transaction!.CurrencyExchange, Is.Not.Null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace RobinTTY.NordigenApiClient.JsonConverters;

internal class SingleOrArrayConverter<TCollection, TItem> : JsonConverter<TCollection>
where TCollection : class, ICollection<TItem>, new()
{
public override TCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;
case JsonTokenType.StartArray:
var list = new TCollection();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray) break;
var listItem = JsonSerializer.Deserialize<TItem>(ref reader, options);
if (listItem != null) list.Add(listItem);
}
return list;
default:
var item = JsonSerializer.Deserialize<TItem>(ref reader, options);
return item != null ? new TCollection {item} : null;
}
}

public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
if (value.Count == 1)
JsonSerializer.Serialize(writer, value.First(), options);
else
{
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
writer.WriteEndArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ public AmountCurrencyPair(decimal amount, string currency)
Amount = amount;
Currency = currency;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace RobinTTY.NordigenApiClient.Models.Responses;
/// </summary>
public class CurrencyExchange
{
/// <summary>
/// The instructed amount including details about the currency the amount is denominated in.
/// </summary>
[JsonPropertyName("instructedAmount")]
public AmountCurrencyPair? InstructedAmount { get; }

/// <summary>
/// Currency from which an amount is to be converted in a currency conversion. ISO 4217 Alpha 3 currency code (e.g.
/// "USD").
Expand Down Expand Up @@ -51,6 +57,9 @@ public class CurrencyExchange
/// <summary>
/// Creates a new instance of <see cref="CurrencyExchange" />.
/// </summary>
/// <param name="instructedAmount">
/// The currency and amount as instructed by the debtor, if the actual settlement is of a different currency or amount
/// </param>
/// <param name="sourceCurrency">
/// Currency from which an amount is to be converted in a currency conversion. ISO 4217 Alpha
/// 3 currency code (e.g. "USD").
Expand All @@ -70,9 +79,10 @@ public class CurrencyExchange
/// </param>
/// <param name="quotationDate">Date at which an exchange rate is quoted.</param>
/// <param name="contractIdentification">Unique identification to unambiguously identify the foreign exchange contract.</param>
public CurrencyExchange(string sourceCurrency, string targetCurrency, string unitCurrency, decimal exchangeRate,
public CurrencyExchange(AmountCurrencyPair? instructedAmount, string sourceCurrency, string targetCurrency, string unitCurrency, decimal exchangeRate,
DateTime? quotationDate, string? contractIdentification)
{
InstructedAmount = instructedAmount;
SourceCurrency = sourceCurrency;
TargetCurrency = targetCurrency;
UnitCurrency = unitCurrency;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using RobinTTY.NordigenApiClient.JsonConverters;
using System.Text.Json.Serialization;

namespace RobinTTY.NordigenApiClient.Models.Responses;

Expand Down Expand Up @@ -218,7 +219,8 @@ public class Transaction
/// Array of the report exchange rate.
/// </summary>
[JsonPropertyName("currencyExchange")]
public IEnumerable<CurrencyExchange>? CurrencyExchange { get; }
[JsonConverter(typeof(SingleOrArrayConverter<List<CurrencyExchange>, CurrencyExchange>))]
public List<CurrencyExchange>? CurrencyExchange { get; }

/// <summary>
/// The identification of the transaction as used for reference by the financial institution.
Expand Down Expand Up @@ -343,7 +345,7 @@ public Transaction(string? transactionId, string? debtorName, MinimalBankAccount
DateTime? valueDateTime, string? remittanceInformationStructured,
IEnumerable<string>? remittanceInformationStructuredArray, string? additionalInformation,
string? additionalInformationStructured, Balance? balanceAfterTransaction, string? checkId,
IEnumerable<CurrencyExchange>? currencyExchange, string? entryReference, string? internalTransactionId,
List<CurrencyExchange>? currencyExchange, string? entryReference, string? internalTransactionId,
string? merchantCategoryCode, DateTime? bookingDateTime)
{
TransactionId = transactionId;
Expand Down
Loading