Recursive AI

Standardize a Canadian address in C#

OneContact is a free, open-source contact data library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies, no network access.

Standardize Canadian postal labels and check address structure, without a delivery-point lookup.

Terminal
dotnet add package RecursiveAI.OneContact

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using RecursiveAI.OneContact;
 
var address = PostalAddress.Parse(
    "10-123 Main Street, Toronto ON m5v2t6",
    Region.CA);
Console.WriteLine(address.ToString(
    AddressStyle.Postal, AddressLayout.Label));
Text
10-123 MAIN ST
TORONTO ON  M5V 2T6

Unzip the example, then dotnet run.

The full program

The example embeds four invented addresses: an English unit-before-street address, a French apartment address, a province/postal-code mismatch and text the rules cannot separate. It prints parsing reasons separately from structural validation issues.

C#
using System.Buffers;
using RecursiveAI.OneContact;
 
string[] inputs =
[
    "10-123 Main Street, Toronto ON m5v2t6",
    "123 rue Principale app 4, Montréal QC h3z2y7",
    "123 Main St, Toronto ON H3Z 2Y7",
    "123 rue Principale Montréal QC H3Z 2Y7"
];
foreach (string input in inputs)
{
    Console.WriteLine($"Input: {input}");
    var status = PostalAddress.TryParse(
        input.AsSpan(), Region.CA,
        out var address, out var detail);
    Console.WriteLine($"Parse: {status}");
    foreach (var issue in detail.Issues)
        Console.WriteLine($"Reason: {issue.Reason}");
    if (status == OperationStatus.Done)
    {
        Console.WriteLine(address.ToString(
            AddressStyle.Postal,
            AddressLayout.Label));
        var issues = address.Validate();
        Console.WriteLine(
            $"Structural issues: {issues.Count}");
        foreach (var issue in issues)
            Console.WriteLine(
                $"{issue.Field}: {issue.Kind}");
    }
    Console.WriteLine();
}

dotnet run prints:

Text
Input: 10-123 Main Street, Toronto ON m5v2t6
Parse: Done
10-123 MAIN ST
TORONTO ON  M5V 2T6
Structural issues: 0
 
Input: 123 rue Principale app 4, Montréal QC h3z2y7
Parse: Done
123 RUE PRINCIPALE APP 4
MONTRÉAL QC  H3Z 2Y7
Structural issues: 0
 
Input: 123 Main St, Toronto ON H3Z 2Y7
Parse: Done
123 MAIN ST
TORONTO ON  H3Z 2Y7
Structural issues: 1
PostalCode: MismatchingValue
 
Input: 123 rue Principale Montréal QC H3Z 2Y7
Parse: InvalidData
Reason: address.model-required

Parse throws ContactParseException when the rules cannot read the input; the full program uses TryParse to get a status and reason instead.

Variations

A French apartment address. Keep a comma or line break between the delivery line and municipality:

C#
using RecursiveAI.OneContact;
 
var address = PostalAddress.Parse(
    "123 rue Principale app 4, Montréal QC h3z2y7",
    Region.CA);
Console.WriteLine(address.ToString(
    AddressStyle.Postal, AddressLayout.Label));
Text
123 RUE PRINCIPALE APP 4
MONTRÉAL QC  H3Z 2Y7

Handle text the rules cannot separate. Inspect the reason without throwing:

C#
using System.Buffers;
using RecursiveAI.OneContact;
 
var status = PostalAddress.TryParse(
    "123 rue Principale Montréal QC H3Z 2Y7".AsSpan(),
    Region.CA, out var address, out var detail);
if (status != OperationStatus.Done)
    Console.WriteLine(detail.PrimaryReason);
Text
address.model-required

The short answer

Use PostalAddress.Parse(text, Region.CA) to read Canadian free text, then ToString(AddressStyle.Postal, AddressLayout.Label) for a standardized label. Call Validate() separately to inspect required fields, province names and postal-code patterns. Successful parsing is not the same as successful validation, and neither establishes deliverability.

The snippet at the top turns Main Street into MAIN ST, preserves unit 10 before civic number 123, and changes m5v2t6 to M5V 2T6. The full program shows why these are separate operations: its third address can be parsed and printed, but its postal-code prefix does not match Ontario.

How do I standardize a Canadian address?

Pass Canadian input directly to the Canadian rules rather than trying a US parser first. OneContact reads the municipality, province and postal code from the end of the address. It accepts province codes and province names in English and French. The country hint supplies context when the content does not determine the country; contradictory content can produce address.country-conflict rather than being silently reinterpreted as Canadian.

For predictable imports, retain commas or line breaks between the delivery line and the municipality. Without separators, the rules need to recognize where the street ends. A French street type before the street name can leave that boundary ambiguous. The final example reports address.model-required; version 1.0 does not supply a trained-model fallback. Ask for a corrected separator or separate fields instead of accepting an invented split.

Standardization does not fill gaps. OneContact does not invent a city from a postal code, infer a province from its prefix or guess a unit designator for a bare #. Preserve the original input alongside the standardized address when users may need to correct it. The US and Canada rules document these decisions and the rule identifiers available in RuleDetail.

What is the Canadian postal address format?

For a civic address, Canada Post’s addressing guidelines place the addressee first, the civic address next, and the municipality, province or territory, and postal code together on the last line.

OneContact’s postal-label renderer uses uppercase letters and standard abbreviations. It puts two spaces between the province and postal code on a label, with one space inside the postal code. The one-line postal rendering uses one space before the code instead. Domestic labels omit the country; the renderer’s includeCountry option adds CANADA when needed.

Canadian units can precede the civic number, as 10-123, or follow the street with a designator. The documented English designators include APT, SUITE and UNIT; French includes APP, BUREAU and UNITÉ. The French variation shows APP 4 staying after the street. Do not translate a French street name into English to make a label: formatting the supplied tokens is a different task from translating an address.

How do I verify a Canadian address?

Start by deciding what “verify” means. Validate() checks the address’s structure: required fields, recognized administrative areas, the postal-code pattern and the prefix expected by the province. It returns field-specific issues, at most one per field, rather than a delivery verdict.

In the full program, Toronto ON H3Z 2Y7 produces PostalCode: MismatchingValue. A structurally sound invented address produces no issues. Neither result comes from a lookup of streets, buildings or residents. The structural validation documentation explicitly separates these checks from existence.

If an application needs confirmation that a delivery point exists, use an appropriate postal reference source as a separate step. Keep the parsing reason and structural issues in distinct fields so a user can tell an unreadable address from a readable address with an inconsistent postal code. An empty issue list is useful for form validation, not evidence that a parcel will arrive.

All onecontact recipes