Skip to main content

Why Kanon Doesn't Support Transformations

Kanon deliberately does not support data transformations. This is a core architectural decision, not a missing feature.

The Distinction

Validation vs Transformation

These are fundamentally different operations:

OperationQuestionExample
Validation"Is this data valid?""hello" is a string checkmark
Transformation"Change this into something else"" hello ""hello"

Mixing both creates complexity and unpredictability.

What Kanon Does

checkmark Validation

import { string, parse } from "pithos/kanon";

parse(string(), " hello ");
// → { success: true, data: " hello " }
// Data returned as-is

checkmark Coercion (Type Conversion Before Validation)

import { coerceNumber, parse } from "pithos/kanon";

parse(coerceNumber(), "123");
// → { success: true, data: 123 }
// Converts type BEFORE validation

Coercion is acceptable because it happens before validation and is about type conversion, not data manipulation.

cross Transformation (Not Supported)

// This doesn't exist in Kanon
string().trim().parse(" hello "); // ❌ No .trim() method
string().toLowerCase().parse("HELLO"); // ❌ No .toLowerCase() method
string().transform(s => s.slice(0, 10)); // ❌ No .transform() method

Why This Choice?

1. Separation of Concerns

Validation and transformation are different responsibilities:

// ❌ Mixed concerns (Zod style)
const schema = z.string().trim().toLowerCase().email();
// Is this validating? Transforming? Both? Unclear.

// ✅ Separated concerns (Kanon style)
const result = parse(string().email(), input);
if (result.success) {
const normalized = result.data.trim().toLowerCase();
}
// Clear: validation first, then transformation

2. Predictability

With Kanon, parse() always returns data exactly as received (if valid):

const input = "  Hello World  ";
const result = parse(string(), input);

if (result.success) {
console.log(result.data === input); // ✅ Always true
}

No surprises, no hidden transformations.

3. Performance

Transformations add runtime overhead:

// Zod: validation + transformation
z.string().trim().toLowerCase().parse(input);
// → Validates, then trims, then lowercases

// Kanon: validation only
parse(string(), input);
// → Validates, returns as-is (faster)

4. Simplicity

Less code = fewer bugs:

  • No transformation logic to maintain
  • No edge cases around transformation order
  • No questions about "when does transformation happen?"

5. Compile-Time > Runtime Philosophy

Transformations are pure runtime operations. Kanon prioritizes what can be done at compile-time (type checking, inference) over runtime magic.

How to Handle Transformations

import { string, parse } from "pithos/kanon";

const result = parse(string().email(), input);

if (result.success) {
// Transform after validation
const normalized = result.data.trim().toLowerCase();
// Use normalized...
}

Benefits:

  • Clear separation of concerns
  • Explicit transformation logic
  • Easy to test and debug

Approach 2: Transform Before Validation

import { string, parse } from "pithos/kanon";

// Transform first
const normalized = input.trim().toLowerCase();

// Then validate
const result = parse(string().email(), normalized);

Benefits:

  • Normalize input before validation
  • Useful for user input sanitization

Approach 3: Use Coercion for Type Conversion

import { coerceNumber, coerceBoolean, parse } from "pithos/kanon";

// Type coercion is built-in
parse(coerceNumber(), "123"); // → { success: true, data: 123 }
parse(coerceBoolean(), "true"); // → { success: true, data: true }

Benefits:

  • Handles common type conversions
  • Useful for API inputs, query params, form data

Approach 4: Use Arkhe Utilities

import { string, parse } from "pithos/kanon";
import { evolve } from "pithos/arkhe/object/evolve";

const result = parse(userSchema, input);

if (result.success) {
// Use Arkhe for complex transformations
const transformed = evolve(result.data, {
name: (s) => s.trim().toLowerCase(),
email: (s) => s.toLowerCase(),
});
}

Benefits:

  • Leverage Pithos ecosystem
  • Declarative transformations
  • Type-safe

What About asZod()?

The asZod() helper provides Zod-compatible API for migration purposes:

import { asZod } from "pithos/kanon/helpers/as-zod";
import { string } from "pithos/kanon";

const schema = asZod(string());

// Zod-like methods available
schema.transform(s => s.trim());
schema.preprocess(s => s.toLowerCase());

But:

  • This is a compatibility layer, not a core feature
  • Adds overhead and complexity
  • Only use for gradual migration from Zod

Comparison with Zod

FeatureZodKanon
Validationcheckmarkcheckmark
Type inferencecheckmarkcheckmark
Transformationscheckmark Built-incross Not supported
.transform()checkmarkcross (use asZod() for migration)
.preprocess()checkmarkcross
.trim(), .toLowerCase()checkmarkcross
Coercioncheckmarkcheckmark
PerformanceGoodBetter (no transformation overhead)
Bundle sizeLargerSmaller

When to Use Zod Instead

Use Zod if you need:

  • Built-in data transformations
  • .transform() and .preprocess() methods
  • String manipulation methods (.trim(), .toLowerCase(), etc.)
  • Complex transformation pipelines in schemas

Kanon is for validation only. If transformations are a core requirement, Zod is the better choice.

Philosophy Alignment

This decision aligns with Pithos core philosophy:

UX > DX > Code elegance

  • UX: No transformation overhead = better performance
  • DX: Clear separation = easier to understand and debug
  • Compile-time > Runtime: Validation at runtime, transformations in application code

See Core Philosophy for more context.

Summary

Kanon validates. It doesn't transform.

This is intentional, not a limitation. It keeps the library:

  • Fast (no transformation overhead)
  • Simple (less code, fewer bugs)
  • Predictable (data returned as-is)
  • Focused (one responsibility: validation)

If you need transformations, handle them in your application logic where they belong.


Related