Quick start
This page covers the core workflow. Install Acanthis, then import the package:
import 'package:acanthis/acanthis.dart';Define a schema
An object schema assigns a schema to each named field.
final account = object({
'name': string().min(2),
'email': string().email(),
'age': integer().gte(18),
});
final Map<String, Object?> value = account.parse({
'name': 'Ada',
'email': 'ada@example.com',
'age': 28,
});
print(value['name']); // AdaAll three fields are required. Unknown keys are stripped from the output. Objects explains how to change either policy.
Parse data
parse() returns the parsed value. Invalid input throws ValidationError.
final age = integer().gte(18);
final int accepted = age.parse(28);
try {
age.parse(16);
} on ValidationError catch (error) {
print(error.issues.first.code); // gte
}Use tryParse() when you want to handle invalid input as a result. It collects issues and returns AcanthisOutcome<T>.
final account = object({'email': string().email()});
final result = account.tryParse({'email': 'invalid'});
if (result.isValid) {
print(result.value); // Parsed output is available here.
} else {
print(result.issues.formatFields()); // Messages grouped under /email.
}An invalid result may contain recovery data that still fails validation. Read .value after a successful result; see recovery values for advanced use.
Check validity
validate() returns a boolean and stops at the first failure.
final email = string().email();
print(email.validate('ada@example.com')); // true
print(email.validate('invalid')); // falseUse tryParse() when you also need messages. Flutter's FormField.validator expects String?, so adapt the result:
final email = string().email();
String? validateEmail(String? input) {
final result = email.tryParse(input);
return result.isValid ? null : result.issues.first.message;
}Pass validateEmail to TextFormField(validator: validateEmail). For async checks and several related fields, follow the Flutter live-form recipe.
Transform values
Checks and transformations run in the order you add them. Use pipe() to change the output type.
final age = string().trim().pipe(integer().gte(18), transform: int.parse);
final int value = age.parse(' 21 ');
print(value); // 21
print(age.tryParse('hello').issues.first.code); // transformThe source must pass before the converter runs. The target then validates the converted value. Read transformations for coercion and defaults.
Validate asynchronously
Add an async check with refineAsync(), then use an async entry point.
final username = string().min(3).refineAsync(CallbackAsyncCheck(
(value, context) async {
// Replace this demonstration with an availability service.
final available = await Future.value(value != 'admin');
if (!available) context.addIssue('usernameTaken');
},
));
final result = await username.tryParseAsync('admin');
print(result.isValid); // falseparseAsync() returns Future<T> and throws on failure. Sync parsing of an async schema throws StateError. Application callback errors propagate through both parsing styles.
Reuse schemas
Derive a stricter schema without changing its base.
final name = string().min(2);
final shortName = name.max(12);
final names = name.list();
final optionalValue = name.nullable();
print(shortName.validate('Ada')); // true
print(names.parse(['Ada', 'Grace'])); // [Ada, Grace]
print(optionalValue.parse(null)); // nullNullability allows a null value. Optional fields allow an object key to be absent. Parsed maps and lists can reuse input containers; copy them when you need independent mutable data.
