wellformed

Transforms

applyTransform, all transform types, and the transform pipeline

Transforms normalize data before validation constraints are evaluated. They're applied in the order they're chained on the builder.

Transform Types

TransformInputOutputDescription
trimstringstringRemove leading/trailing whitespace
collapse_whitespacestringstringMultiple spaces to single space + trim
digits_onlystringstringStrip all non-digit characters
upperstringstringConvert to uppercase
lowerstringstringConvert to lowercase
money_to_centsstring/numbernumberParse money string, multiply by 10^scale
date_parsestringstringParse date to canonical YYYY-MM-DD
replacestringstringReplace every occurrence of a literal substring
phone_usstringstringNormalize a US phone number to (XXX) XXX-XXXX when possible
phone_e164stringstringNormalize a phone number to E.164 when possible
format_ssn / format_einstringstringFormat 9 digits as SSN or EIN
mask_ssn / mask_einstringstringMask all but the trailing identifier digits
card_mask_last4stringstringMask a card number except the final four digits
format_credit_cardstringstringGroup card digits in blocks of four
format_ibanstringstringGroup IBAN characters in blocks of four
format_thousandsstring/numberstringAdd thousands separators
format_decimalstring/numberstringFormat to a fixed number of decimal places
normalize_flight_numberstringstringRemove separators and uppercase flight numbers
normalize_icd10 / normalize_cpt / normalize_hcpcs / normalize_ndc11stringstringNormalize healthcare code formats when possible
defaultnull/undefinedanyProvide default value

Using Transforms in Builders

// String transforms
w.string().trim()                          // "  hello  " → "hello"
w.string().collapseWhitespace()            // "a   b   c" → "a b c"
w.string().digitsOnly()                    // "123-45-6789" → "123456789"
w.string().upper()                         // "hello" → "HELLO"
w.string().lower()                         // "HELLO" → "hello"
w.string().replace("-", "")                // "12-34" → "1234"
w.string().default("N/A")                  // null → "N/A"
w.string().formatPhoneUS()                 // "4155551212" → "(415) 555-1212"
w.string().formatPhoneE164()               // "4155551212" → "+14155551212"
w.string().formatSsn()                     // "123456789" → "123-45-6789"
w.string().maskSsn()                       // "123456789" → "***-**-6789"
w.string().formatCreditCard()              // "4242424242424242" → "4242 4242 4242 4242"
w.string().maskCardNumber()                // "4242424242424242" → "************4242"

// Number defaults
w.number().default(0)                      // null → 0
w.integer().default(0)
w.money().default(0)

Transform Chaining

Transforms execute left to right:

w.string()
  .trim()           // "  123-45-6789  " → "123-45-6789"
  .digitsOnly()     // "123-45-6789" → "123456789"
  .ssn()            // Validate as SSN

applyTransform()

Apply a single transform programmatically:

import { applyTransform } from "wellformed-ts";

applyTransform("  hello  ", { fn: "trim" });
// "hello"

applyTransform("123-45-6789", { fn: "digits_only" });
// "123456789"

applyTransform("hello", { fn: "upper" });
// "HELLO"

applyTransform("$1,234.56", { fn: "money_to_cents" });
// 123456

applyTransform("$1,234.56", { fn: "money_to_cents", scale: 3 });
// 1234560

applyTransform("01/15/2024", { fn: "date_parse", format: "MM/DD/YYYY" });
// "2024-01-15"

applyTransform("hello world", { fn: "replace", pattern: "world", replacement: "earth" });
// "hello earth"

applyTransform("4155551212", { fn: "phone_e164" });
// "+14155551212"

applyTransform("123456789", { fn: "mask_ssn" });
// "***-**-6789"

applyTransform("4242424242424242", { fn: "format_credit_card" });
// "4242 4242 4242 4242"

applyTransform(1234.5, { fn: "format_decimal", places: 2 });
// "1234.50"

applyTransform(null, { fn: "default", value: "fallback" });
// "fallback"

applyTransforms()

Apply multiple transforms in sequence:

import { applyTransforms } from "wellformed-ts";

applyTransforms("  $1,234.56  ", [
  { fn: "trim" },
  { fn: "money_to_cents" },
]);
// 123456

money_to_cents

Converts a money string (or number) to an integer in the smallest currency unit:

// String input: strips $, commas, parses float, multiplies by 10^scale
applyTransform("$1,234.56", { fn: "money_to_cents" });          // 123456
applyTransform("$1,234.56", { fn: "money_to_cents", scale: 2 }); // 123456 (default)
applyTransform("$1,234.56", { fn: "money_to_cents", scale: 0 }); // 1235

// Number input: multiplies directly
applyTransform(1234.56, { fn: "money_to_cents" });               // 123456

date_parse

Parses dates from various formats to canonical YYYY-MM-DD:

applyTransform("01/15/2024", { fn: "date_parse", format: "MM/DD/YYYY" });
// "2024-01-15"

applyTransform("01-15-2024", { fn: "date_parse", format: "MM-DD-YYYY" });
// "2024-01-15"

applyTransform("01152024", { fn: "date_parse", format: "MMDDYYYY" });
// "2024-01-15"

applyTransform("2024-01-15", { fn: "date_parse", format: "YYYY-MM-DD" });
// "2024-01-15" (already canonical)

Transforms in the IR

In the serialized JSON IR, transforms are stored as an array on each field:

{
  "type": "string",
  "transforms": [
    { "fn": "trim" },
    { "fn": "digits_only" }
  ],
  "constraints": [...]
}

The fn field identifies the transform type, matching the Rust crate's #[serde(tag = "fn")] format.

On this page