Skip to content

Transformations ​

Validasi provides two transformation mechanisms:

  • Transform modifier rule: changes values inside schema validation.
  • withPreprocess(...): converts raw input before type checking and rules.

Use them together when needed, but for different responsibilities.

1) Transform Modifier ​

Use Transform when the input type is already correct and you want to normalize data before validation rules.

dart
import 'package:validasi/validasi.dart';
import 'package:validasi/rules.dart';

final emailSchema = Validasi.string([
  Rules.transform<String>((value) => value?.trim()),
  Rules.transform<String>((value) => value?.toLowerCase()),
  Rules.string.minLength(5),
]);

final result = emailSchema.validate('  USER@EXAMPLE.COM  ');
print(result.data); // "user@example.com"

How it works:

  • Runs as part of rule execution.
  • The transformed value is passed to subsequent rules.
  • The final transformed value is returned in result.data.
  • Transform runs on null values too, so null-safe code is recommended.

2) Preprocess Transformation ​

Use withPreprocess when raw input may be a different type than the schema expects. The key benefit: validate() parameter type is updated at compile time to match the preprocess input type.

dart
import 'package:validasi/validasi.dart';
import 'package:validasi/rules.dart';

// Create base schema validating int
final ageSchema = Validasi.number<int>([
  Rules.number.moreThanEqual(0),
]);

// Add preprocessing: accepts String, converts to int
final ageSchemaWithPreprocess = ageSchema.withPreprocess(
  (String value) => int.parse(value),
);

// Now validate() accepts String, not int
print(ageSchemaWithPreprocess.validate('42').data); // 42

How it works:

  • Preprocessing runs before type checking.
  • withPreprocess() returns a new engine where validate() accepts the transformation input type.
  • If preprocessing fails, validation fails with a Preprocess error.
  • If preprocessing succeeds but returns the wrong type, validation fails at type check.
  • Type enforcement is compile-time: passing wrong input type causes compiler error.

Execution Order ​

Validation flow is:

  1. withPreprocess(...) transformation (if configured)
  2. Type check against schema type
  3. Rule execution, including Rules.transform<String>(...)
  4. Final ValidasiResult

This means:

  • Use preprocess for parsing/conversion into the expected type.
  • Use Transform for cleanup/normalization after conversion.

Proper Usage Guidelines ​

  • Use preprocess to parse external input: JSON strings, query params, form strings.
  • Use Transform for schema-level normalization: trim, lowercase, formatting.
  • Keep transformations pure: return transformed data, avoid side effects.
  • Put Transform before other validation rules that depend on normalized values.
  • Keep transforms null-safe when using nullable schemas.
  • When accepting dynamic input, create ValidasiEngine<T, dynamic> or use withPreprocess to define the allowed input type.
  • Use explicit type annotations on transformation functions for compile-time safety: .withPreprocess((InputType value) => ...).

Combined Example: Type-Safe Transformation ​

dart
// Schema validates String and applies Transform rules
final usernameSchema = Validasi.string([
  Rules.nullable<String>(),
  Rules.transform<String>((value) => value?.trim()),
  Rules.transform<String>((value) => value?.toLowerCase()),
  Rules.string.minLength(3),
]);

// Add preprocessing to accept dynamic input and convert to String
final usernameSchemaWithPreprocess = usernameSchema.withPreprocess(
  (dynamic value) => value.toString(),
);

// Now validate() accepts dynamic input with compile-time flexibility
print(usernameSchemaWithPreprocess.validate(1234).data);       // "1234"
print(usernameSchemaWithPreprocess.validate('  John  ').data); // "john"

Handling Dynamic Inputs ​

When you need to accept values of unknown type at compile time, two approaches:

Approach 1: Use dynamic as input type

dart
// Engine<OutputType, dynamic> accepts any input
final schema = ValidasiEngine<int, dynamic>([
  Rules.number.moreThan(0),
]);

schema.validate(42);     // OK: int
schema.validate('42');   // Runtime TypeCheck error (no preprocess)

Approach 2: Use withPreprocess (recommended)

dart
// Explicit preprocessing handles type conversion
final schema = Validasi.number<int>([
  Rules.number.moreThan(0),
]).withPreprocess(
  (String s) => int.parse(s),
);

schema.validate('42'); // OK: compile-time guarantees String input

Released under the MIT License.