Schema Types in Ack
This guide covers all the built-in schema types in Ack and their specific validation options.
String Schema
Create string validators with various constraints:
// Basic string schema
final nameSchema = Ack.string;
// String with length constraints
final usernameSchema = Ack.string
.minLength(3)
.maxLength(20)
.matches(r'[a-zA-Z0-9_]+', example: 'user_123'); // Regex: letters, numbers, underscore
// Email validation
final emailSchema = Ack.string.isEmail();
// Enum values (select from options)
final roleSchema = Ack.string.isEnum(['admin', 'user', 'guest']);
// Note: For more constraints (e.g., regex patterns), see the [Validation](./validation.mdx) page.
// For custom constraints, see the [Custom Validation](../guides/custom-validation.mdx) page.
Number Schemas
Create validators for integer and decimal values:
// Integer validation
final ageSchema = Ack.int
.min(0)
.max(120);
// Double/decimal validation
final priceSchema = Ack.double
.min(0.0, exclusive: true) // Must be greater than 0.0
.multipleOf(0.01); // Allow only 2 decimal places
// Note: See the [Validation](./validation.mdx) page for more number constraints (e.g., positive, negative).
Date and Time Validation
Validate date and time values using specialized string constraints:
// Basic date validation (YYYY-MM-DD)
final birthdateSchema = Ack.string.isDate();
// Date-time validation (ISO 8601 format)
final appointmentSchema = Ack.string.isDateTime();
// For date range validation or custom formats, see the [Custom Validation](../guides/custom-validation.mdx) page.
List Schema
Validate arrays and lists containing elements of a specific schema type:
// List of strings
final tagsSchema = Ack.list(Ack.string);
// List with constraints on the list itself
final itemsSchema = Ack.list(Ack.string)
.minItems(1) // Must have at least 1 item
.maxItems(10) // Must have at most 10 items
.uniqueItems(); // All items must be unique
// List of complex objects (e.g., a list of users)
final usersListSchema = Ack.list(
Ack.object({
'id': Ack.int.positive(),
'name': Ack.string
}, required: ['id', 'name'])
);
Map Schema (Using Object Schema)
While Ack doesn't have a dedicated Ack.map
schema, you can validate key-value maps using an Ack.object
with additionalProperties
:
// Map with string keys to integer values (e.g., scores)
final scoresSchema = Ack.object(
{}, // No predefined properties expected
additionalProperties: Ack.int.min(0).max(100) // All values must be ints between 0-100
);
// Validate a scores map
final result = scoresSchema.validate({'math': 95, 'history': 88});
print(result.isOk); // true
final invalidResult = scoresSchema.validate({'english': 105}); // Value out of range
print(invalidResult.isOk); // false
Object Schema
Validate structured data objects with defined properties and types. This is one of the most common schema types.
// User object schema
final userSchema = Ack.object(
{
// Define properties and their schemas
'name': Ack.string.minLength(1),
'age': Ack.int.min(0),
'email': Ack.string.isEmail(),
// Nested object for address
'address': Ack.object(
{
'street': Ack.string,
'city': Ack.string,
'zipCode': Ack.string.matches(r'\d{5}', example: '12345'), // Simple 5-digit zip code regex
},
required: ['street', 'city'], // Street and city are required *within* the address object
),
},
// Specify required top-level properties for the *user* object
required: ['name', 'email'],
);
Optional Fields
By default, fields defined in an Ack.object
are optional unless included in the required
list.
You can also make any field explicitly nullable using .nullable()
. See Making Schemas Nullable below.
final productSchema = Ack.object(
{
'id': Ack.string, // Required
'name': Ack.string, // Required
'description': Ack.string, // Optional (not in 'required' list)
'price': Ack.double.min(0), // Required
'tags': Ack.list(Ack.string).nullable(), // Optional list (can be null)
},
required: ['id', 'name', 'price'],
);
// Valid: description is missing, tags is null
final validData = {'id': 'p1', 'name': 'Thing', 'price': 9.99, 'tags': null};
print(productSchema.validate(validData).isOk); // true
// Invalid: name is missing
final invalidData = {'id': 'p2', 'price': 19.99};
print(productSchema.validate(invalidData).isOk); // false
Handling Nested Models
When working with nested objects or lists of objects, Ack handles the validation recursively. If you are using code generation or the SchemaModel
class, accessing nested data is straightforward.
Accessing Nested Data:
Assuming you have validated data or a model instance (user
):
// Direct access (if using generated models or SchemaModel)
// The type is automatically converted.
Address billingAddress = user.billingAddress;
print(billingAddress.city);
// Accessing items in a list of models
List<OrderItem> items = user.orderItems;
for (final item in items) {
print(item.productName);
}
// If working directly with validated data (Map<String, dynamic>)
final validatedData = result.getOrThrow();
final addressMap = validatedData['address'] as Map<String, dynamic>?;
if (addressMap != null) {
// You might need to validate/parse the nested part separately
// if not using generated models.
final nestedAddressSchema = Ack.object({...}); // Define address schema again
final addressResult = nestedAddressSchema.validate(addressMap);
if (addressResult.isOk) {
final city = addressResult.getOrThrow()['city'];
print(city);
}
}
Updating Nested Data (Immutability):
When updating, it's best practice to create new instances rather than modifying existing ones, especially if using immutable models.
// Assume 'user' is an existing immutable model instance
final updatedUser = user.copyWith(
// Create a new Address instance with the changed city
address: user.address.copyWith(city: "Miami")
);
// If using generated schemas, you might convert back for re-validation
// final updatedSchema = UserSchema.fromModel(updatedUser);
// final validationResult = UserSchema.schema.validate(updatedSchema.toMap());
For more details on using generated schemas (like UserSchema.fromModel
), see the Code Generation guide.
Allowing Additional Properties
By default, Ack.object
ignores properties in the input data that are not defined in the schema. To capture these, use additionalProperties
:
final flexibleSchema = Ack.object(
{
'id': Ack.int,
},
// Allow any other properties, as long as their values are strings
additionalProperties: Ack.string,
);
final data = {'id': 1, 'name': 'Gadget', 'color': 'blue', 'status': 123};
final result = flexibleSchema.validate(data);
print(result.isOk); // false (because 'status' is not a string)
final validated = flexibleSchema.validate({'id': 1, 'name': 'Gadget'}).getOrThrow();
print(validated); // {id: 1, name: Gadget} - Extra properties are included
To disallow any extra properties, set additionalProperties: false
:
final strictSchema = Ack.object(
{
'id': Ack.int,
'name': Ack.string,
},
additionalProperties: false,
);
final dataWithExtra = {'id': 1, 'name': 'Test', 'extra': 'disallowed'};
final result = strictSchema.validate(dataWithExtra);
print(result.isOk); // false
print(result.getError()?.message); // Contains info about disallowed property 'extra'
Making Schemas Nullable
Any schema type can be made nullable by appending .nullable()
. This means the schema will accept both its defined type and the value null
.
// Nullable string
final middleNameSchema = Ack.string.nullable();
// Nullable date
final optionalDateSchema = Ack.string.isDate().nullable();
// Nullable object (the entire address object can be null)
final optionalAddressSchema = Ack.object(
{
'street': Ack.string,
'city': Ack.string,
},
required: ['street', 'city'],
).nullable(); // Note .nullable() is outside Ack.object
// --- Validation Examples ---
// String checks
print(middleNameSchema.validate(null).isOk); // true
print(middleNameSchema.validate('Robert').isOk); // true
// Object checks
print(optionalAddressSchema.validate(null).isOk); // true (null object is allowed)
final validAddress = {'street': '1 Elm St', 'city': 'Gotham'};
print(optionalAddressSchema.validate(validAddress).isOk); // true (valid address object)
// Invalid nested object still fails even if object is nullable
final invalidAddress = {'street': '1 Elm St'}; // Missing required 'city'
print(optionalAddressSchema.validate(invalidAddress).isOk); // false