Skip to main content

Troubleshooting & FAQ

Common issues encountered when working with RA.Utilities.Core.ValueObjects and guidance on how to resolve them.


Common Issues​

Why did deserialization throw a JsonException instead of my value object type?​

Cause: The built-in converter (ValueObjectJsonConverter<T>) catches validation failures during deserialization and wraps them in a JsonException. This is by design: malformed JSON inputs must not instantiate invalid domain objects.

try
{
var account = JsonSerializer.Deserialize<BankAccount>(json);
}
catch (JsonException ex)
{
// ex.Message contains the failed validation error details
}

Fix: Validate client input before passing it to deserialization, or use endpoint validation filters in your API framework.


Email.Parse throws even though the string "looks valid" — why?​

Cause: Validation is performed on the trimmed, lowercased string. Common reasons for rejection:

  • Value is whitespace or empty → BaseErrorCode.EmailRequired (REQUIRED_EMAIL)
  • Exceeds 254 characters (RFC 5321 limit) → BaseErrorCode.EmailMaxLength (EMAIL_MAX_LENGTH)
  • Missing @, missing local part, missing domain, or domain lacks a dot → BaseErrorCode.EmailNotValid (INVALID_EMAIL)

Fix: Ensure the input matches the standard format (name@example.com). Refer to the Email API Reference.


Why did an IBAN throw BadRequestException with IbanCheckDigits?​

Cause: The account number failed the ISO 7064 mod-97 checksum calculation. Even a single transposed or mistyped digit alters the checksum remainder.

// ❌ Check digits 00 do not match expected mod-97 check digits
new IBAN("GB00WEST12345698765432"); // throws BadRequestException (IbanCheckDigits)

// ✅ Check digits 82 match the mod-97 algorithm
new IBAN("GB82WEST12345698765432");

Fix: Verify that the account number was entered correctly by the user. The exception's ValidationError includes ExpectedValue with the correct check digits for that BBAN.


Why does IBAN accept a 20-character German IBAN if DE IBANs are 22 characters?​

Cause: IBAN performs international structural validation (13 to 34 characters) and ISO 7064 mod-97 check digit verification. It does not hardcode per-country length registries into the core library, ensuring forward-compatibility with international banking changes.

Fix: If your application enforces national length rules, validate the length in your application layer using BaseErrorCode.IbanLength (IBAN_LENGTH):

using RA.Utilities.Core.Constants;
using RA.Utilities.Core.Exceptions;
using RA.Utilities.Core.ValueObjects;

var iban = new IBAN(input);
if (iban.CountryCode == "DE" && iban.Value.Length != 22)
{
throw new BadRequestException(new ValidationError(BaseErrorMessage.IbanLength)
{
PropertyName = "IBAN",
ErrorCode = BaseErrorCode.IbanLength,
AttemptedValue = iban.Value,
ExpectedValue = "22"
});
}

My two Currency or IBAN instances aren't equal — but the strings look the same?​

Cause: Equality compares the normalized Value property. While whitespace and casing are normalized automatically:

  • Different characters or invisible unicode characters will prevent equality.
  • Currencies must match ISO 4217 uppercase strings.
var a = Currency.Parse("EUR", provider: null);
var b = new Currency(" eur ");

Console.WriteLine(a == b); // true — normalized to "EUR"

I get a BadRequestException from a constructor — is that expected?​

Cause: Yes. Unlike standard .NET types that throw ArgumentException, value objects in this library throw BadRequestException populated with structured ValidationError records.

Fix: Let the exception bubble up to the RA.Utilities.Api global error handler, which converts it to a standard HTTP 400 Bad Request response. If checking user input conditionally without exceptions, use TryParse.


Can I store these value objects in Entity Framework Core?​

Yes. Because string-backed value objects expose a single normalized Value property, configure them in EF Core with a simple value converter:

modelBuilder.Entity<Customer>(entity =>
{
entity.Property(e => e.Email)
.HasConversion(email => email.Value, str => new Email(str));

entity.Property(e => e.Iban)
.HasConversion(iban => iban.Value, str => new IBAN(str));
});

For Money, map the composite properties using EF Core's ComplexProperty or OwnsOne.


Why can't I assign a string directly to a value object parameter?​

Cause: There is intentionally no implicit conversion from string to any value object. Allowing implicit conversion would bypass validation and reintroduce primitive obsession bugs.

Fix: Explicitly instantiate the value object or use Parse / TryParse:

// ❌ Does not compile by design
// Email email = "user@gmail.com";

// ✅ Explicit creation with boundary validation
Email email = new("user@gmail.com");

// ✅ Implicit conversion to string is supported (lossless)
string raw = email;

Known Limitations​

  • Format verification over registry checks:
    • Currency validates that the code is exactly 3 characters; it does not check against a dynamic ISO currency list.
    • IBAN validates the 13–34 character structure and ISO 7064 mod-97 check digits; it does not hardcode per-country lengths or domestic clearing codes.
  • Money JSON serialization: Email, Currency, SSN, and IBAN serialize as plain strings via ValueObjectJsonConverter<T>. Money has two properties (Amount and Currency) and serializes as a standard JSON object.
  • Normalization is irreversible: Trimming and canonical casing (lowercase for Email, uppercase for Currency, SSN, and IBAN, stripping separators for IBAN) modify input formatting permanently.

Performance Tips​

  • Use TryParse for user-facing API validation: When accepting input from untrusted clients where invalid input is expected, use TryParse to avoid exception throwing and stack trace generation.
  • Records are lightweight: Value objects are immutable sealed records with zero heap allocations beyond the backing string. Equality comparisons evaluate the underlying string.