Bai2.Net
1.1.0
dotnet add package Bai2.Net --version 1.1.0
NuGet\Install-Package Bai2.Net -Version 1.1.0
<PackageReference Include="Bai2.Net" Version="1.1.0" />
<PackageVersion Include="Bai2.Net" Version="1.1.0" />
<PackageReference Include="Bai2.Net" />
paket add Bai2.Net --version 1.1.0
#r "nuget: Bai2.Net, 1.1.0"
#:package Bai2.Net@1.1.0
#addin nuget:?package=Bai2.Net&version=1.1.0
#tool nuget:?package=Bai2.Net&version=1.1.0
Bai2.Net
Zero-dependency BAI2 (Bank Administration Institute, version 2) statement parser for .NET. Reads cash-management and lockbox files into a typed model, and reconciles every control total for you.
MIT Zero dependencies Native AOT clean
Why
BAI2 is still the dominant machine-readable format US banks use to report balances and transactions. If you move money in the United States, sooner or later a bank hands you a BAI2 file. The options today are thin: pull in a stale hobbyist parser, or hand-roll the record hierarchy and get the control totals subtly wrong. Bai2.Net is the small, correct primitive, and a sibling to Mt940.Net and Camt.Net so the whole bank-statement cluster parses the same way.
The headline feature is reconciliation. A BAI2 file states a control total at the account, group, and file level, and a record count at each. Bai2.Net recomputes all of them from the records and tells you when they disagree, so a truncated or tampered file fails loudly instead of quietly under-reporting cash.
Install
dotnet add package Bai2.Net
Use
using Bai2;
Bai2File file = Bai2Statement.Parse(content);
FileHeader header = file.Header;
Account account = file.Groups[0].Accounts[0];
foreach (Transaction t in account.Transactions)
{
// Money is exposed twice: exact minor units, and a decimal convenience.
Console.WriteLine($"{t.TypeCode} {t.AmountMinor} ({t.Amount:0.00}) {t.FundsType}");
}
AmountMinor is a long in the currency's smallest unit (for example cents). Amount is the same value as a decimal in major units. Money is never a double.
Currency minor-units exponent
Amount is derived from AmountMinor by dividing by ten to the currency's minor-units exponent. The default exponent is 2 (divide by 100), which matches USD, EUR, GBP and most currencies. Pass minorUnitsExponent to override it:
// JPY has no minor unit (exponent 0): Amount equals AmountMinor.
Bai2File jpy = Bai2Statement.Parse(content, minorUnitsExponent: 0);
// A three-decimal currency (for example BHD, KWD).
Bai2File bhd = Bai2Statement.Parse(content, minorUnitsExponent: 3);
AmountMinor is always exact and never affected by the exponent, so control-total reconciliation is unchanged. The override is available on Parse(string, ...), Parse(Stream, ...) and TryParse.
Reconcile the control totals
Bai2File file = Bai2Statement.Parse(content);
file.Validate(); // throws Bai2ValidationException if any total or count does not add up
// Or reconcile as part of parsing:
Bai2File reconciled = Bai2Statement.Parse(content, validate: true);
Reconciliation uses checked arithmetic, so amounts that would overflow long when summed are reported as an error rather than silently wrapping.
Validate() checks that:
- each account control total (record
49) equals the sum of that account's summary and transaction amounts, - each group control total (record
98) equals the sum of its account totals, - the file control total (record
99) equals the sum of its group totals, and - every declared record count and account count matches what was parsed.
Empty (omitted) counts are skipped rather than treated as zero.
Try-parse and errors
if (Bai2Statement.TryParse(content, out Bai2File? parsed))
{
// parsed is non-null here
parsed.Validate();
}
Parse throws Bai2ParseException on malformed structure; its message names the 1-based physical record that failed. TryParse returns false instead. Bai2Statement.Parse(Stream) reads UTF-8.
The model
Bai2File
FileHeader Header
Group[] Groups
GroupHeader Header
Account[] Accounts
AccountSummary[] Summaries (balances and status items from record 03)
Transaction[] Transactions (record 16, with 88 continuations folded into Text)
AccountTrailer Trailer
GroupTrailer Trailer
FileTrailer Trailer
The record hierarchy is 01 File Header, 02 Group Header, 03 Account Identifier, 16 Transaction Detail (with 88 continuations), 49 Account Trailer, 98 Group Trailer, 99 File Trailer. Continuation (88) records are folded into the record they extend before you see the model: for a transaction they append to Text; for an account identifier they extend the summary list.
Dates
Dates are DateOnly, parsed from the BAI2 YYMMDD fields. The two-digit year uses a fixed pivot: 00-69 map to the 2000s, 70-99 map to the 1900s. Times stay as the raw HHMM string, because BAI2 does not carry a time zone.
Limitations
- Targets BAI2 (version 2). It parses the common record set above and the common type and funds-type codes; it does not interpret every bank-specific type code.
- Amount conversion to the
decimalconvenience assumes a 2-digit currency exponent (divide minor units by 100) by default. For a currency with a different exponent, passminorUnitsExponenttoParse/TryParse(for example0for JPY,3for a three-decimal currency).AmountMinoris always exact regardless of the exponent. - Funds types that carry sub-fields (
Vvalue-dated,Ddistribution list,Sdistributed) are recorded as their code; the trailing sub-fields are not expanded. - A trailer (
49,98,99) with an empty control-total field is rejected withBai2ParseExceptionrather than being read as zero, since a blank control total is a truncated file, not a reconciled one. Trailer record and account counts remain optional. - Continuation (
88) records are accepted only after an account identifier (03) or a transaction detail (16); a stray88after any other record is aBai2ParseException.
License
MIT, Israel Iyonsi.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.