Input Validate
A powerful Dart package for validating nested map data structures with Laravel-style validation rules and wildcard path support. Perfect for validating JSON data, form inputs, API payloads, and complex nested objects.
Features
β¨ Laravel-style validation - Familiar validation syntax inspired by Laravel's request validation
π― Wildcard path support - Validate arrays and nested structures with users.*.email
notation
π Performance optimized - Parallel validation and caching for high-performance applications
π Async rule support - Full support for asynchronous validation rules
π Comprehensive error reporting - Detailed field-specific error messages with path information
π¨ Type-safe - Built with Dart's strong type system for reliability
π Only validated data - Returns only the fields that passed validation, stripping unvalidated data
π Debug logging - Comprehensive logging for debugging validation process and performance analysis
Installation
Add this package to your pubspec.yaml
:
dependencies:
input_validate: ^1.0.0
Then run:
dart pub get
Quick Start
Basic Validation
import 'package:input_validate/input_validate.dart';
// β¨ NEW: Concise syntax (recommended)
final rules = {
'name': [required(), string()],
'age': [required(), number(), min(18)],
'email': [required(), email()],
};
// π Or use verbose syntax (backward compatible)
final verboseRules = {
'name': [RequiredRule(), IsStringRule()],
'age': [RequiredRule(), IsNumberRule(), MinRule(18)],
'email': [RequiredRule(), EmailRule()],
};
final input = {
'name': 'John Doe',
'age': 25,
'email': 'john@example.com',
'password': 'secret', // This will be stripped from output
};
try {
final validated = await InputValidate.validate(input, rules);
print(validated);
// Output: {name: John Doe, age: 25, email: john@example.com}
// Notice: password field is stripped because it wasn't validated
} catch (e) {
if (e is MultipleValidationException) {
print('Validation errors: ${e.inputErrors}');
}
}
Nested Object Validation
// Concise syntax
final rules = {
'user.name': [required(), string()],
'user.profile.age': [required(), number(), min(18)],
'settings.theme': [required(), inSet({'light', 'dark'})],
};
final input = {
'user': {
'name': 'Alice',
'profile': {'age': 28},
},
'settings': {'theme': 'dark'},
};
final validated = await InputValidate.validate(input, rules);
// Returns only the validated nested structure
Array Validation with Wildcards
// Concise syntax for array validation
final rules = {
'users.*.name': [required(), string()],
'users.*.email': [required(), email()],
'users.*.roles': [required(), list()],
};
final input = {
'users': [
{
'name': 'Bob',
'email': 'bob@example.com',
'roles': ['admin'],
'password': 'secret', // Will be stripped
},
{
'name': 'Carol',
'email': 'carol@example.com',
'roles': ['user'],
},
],
};
final validated = await InputValidate.validate(input, rules);
// Returns users array with only validated fields
Nested Wildcard Validation
final rules = {
'departments.*.name': [RequiredRule(), IsStringRule()],
'departments.*.employees.*.name': [RequiredRule(), IsStringRule()],
'departments.*.employees.*.salary': [RequiredRule(), IsNumberRule()],
};
// Validates deeply nested arrays and objects
Validation Syntax Options
InputValidate offers two syntax styles for defining validation rules:
π Concise Syntax (Recommended)
The new concise syntax provides cleaner, more readable validation rules:
final rules = {
'name': [required(), string(), min(2), max(50)],
'age': [required(), number(), min(18), max(120)],
'email': [required(), string(), email()],
'status': [required(), string(), inSet({'active', 'inactive'})],
'tags': [required(), list(), min(1), max(5)],
'profile.bio': [nullable(), string(), max(500)],
};
Available shorthand functions:
- Type validation:
required()
,string()
,number()
,boolean()
,list()
,map()
,email()
,nullable()
- Constraints:
min(value)
,max(value)
,inSet(values)
- Aliases:
isString()
,isNumber()
,isBoolean()
,isList()
,isMap()
,allowedValues(values)
π Verbose Syntax (Backward Compatible)
The original class-based syntax remains fully supported:
final rules = {
'name': [RequiredRule(), IsStringRule(), MinRule(2), MaxRule(50)],
'age': [RequiredRule(), IsNumberRule(), MinRule(18), MaxRule(120)],
'email': [RequiredRule(), IsStringRule(), EmailRule()],
'status': [RequiredRule(), IsStringRule(), InRule({'active', 'inactive'})],
'tags': [RequiredRule(), IsListRule(), MinRule(1), MaxRule(5)],
'profile.bio': [NullableRule(), IsStringRule(), MaxRule(500)],
};
Mixing both syntaxes is supported:
final rules = {
'name': [required(), IsStringRule()], // Mixed syntax works fine
'age': [RequiredRule(), number(), min(18)], // Any combination
};
Available Validation Rules
Type Rules
RequiredRule()
/required()
- Field must be present and not null/emptyIsStringRule()
/string()
- Value must be a stringIsNumberRule()
/number()
- Value must be a number (int or double)IsBooleanRule()
/boolean()
- Value must be a booleanIsListRule()
/list()
- Value must be a listIsMapRule()
/map()
- Value must be a map
Constraint Rules
MinRule(value)
/min(value)
- Minimum value for numbers or minimum length for strings/listsMaxRule(value)
/max(value)
- Maximum value for numbers or maximum length for strings/listsInRule(allowedValues)
/inSet(allowedValues)
- Value must be in the allowed set
Format Rules
EmailRule()
/email()
- Value must be a valid email address
Special Rules
NullableRule()
/nullable()
- Allows null values and skips other validation rules when value is null
Working with Nullable Fields
The NullableRule
provides special behavior for handling optional fields. When a field value is null
and NullableRule
is present in the validation rules, all other validation rules for that field are skipped.
final rules = {
// Required field - null not allowed
'user.name': [RequiredRule(), IsStringRule()],
// Optional field - can be null or valid string
'user.email': [
NullableRule(), // Allow null values
IsStringRule(), // Only applied if value is not null
EmailRule(), // Only applied if value is not null
],
// Optional field with constraints
'user.bio': [
NullableRule(), // Allow null values
IsStringRule(), // Only applied if value is not null
MinRule(10), // Only applied if value is not null
MaxRule(500), // Only applied if value is not null
],
};
final input = {
'user': {
'name': 'John Doe',
'email': null, // β
Passes validation (NullableRule allows null)
'bio': null, // β
Passes validation (NullableRule allows null)
}
};
final result = await InputValidate.validate(input, rules);
// Returns: {'user': {'name': 'John Doe', 'email': null, 'bio': null}}
Important: If both RequiredRule
and NullableRule
are present, RequiredRule
takes precedence and null values will fail validation.
final rules = {
'field': [RequiredRule(), NullableRule(), IsStringRule()], // β RequiredRule prevents null
};
Performance Optimization
Parallel Validation (Default)
// Runs field validations in parallel for better performance
final result = await InputValidate.validate(
input,
rules,
enableParallelValidation: true, // Default
);
Sequential Validation with Early Termination
// Validates fields sequentially with early termination on RequiredRule failures
final result = await InputValidate.validate(
input,
rules,
enableParallelValidation: false,
);
Field Path Caching
The package automatically caches parsed field paths for improved performance on repeated validations.
Debug Logging
The package includes comprehensive debug logging to help you understand the validation process and troubleshoot issues. All logs use Dart's built-in log()
function from dart:developer
.
Viewing Debug Logs
In development, you can view debug logs.
Error Handling
try {
final validated = await InputValidate.validate(input, rules);
} catch (e) {
if (e is MultipleValidationException) {
print('Failed fields: ${e.failureCount}');
// Access detailed error information
for (final entry in e.inputErrors!.entries) {
final fieldPath = entry.key;
final errors = entry.value;
print('$fieldPath: ${errors.join(', ')}');
}
}
}
Example Error Output
name: This field is required
email: This field must be a valid email address
users.1.age: This field must be at least 18
users.2.email: This field must be a valid email address
Creating Custom Rules
class CustomPasswordRule implements ValidationRule {
@override
String get message => 'Password must contain uppercase, lowercase, number, and special character.';
@override
FutureOr<bool> passes(dynamic value) {
if (value is! String || value.length < 8) return false;
return RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]').hasMatch(value);
}
}
// Usage
final rules = {
'password': [RequiredRule(), CustomPasswordRule()],
};
Key Benefits
π― Data Security - Only validated fields are returned, preventing data leaks
β‘ Performance - Parallel validation and intelligent caching
π Debugging - Detailed error messages with exact field paths
π‘οΈ Type Safety - Leverages Dart's type system for reliability
π Scalability - Efficient handling of large, complex data structures
Wildcard Path Patterns
Pattern | Description | Example |
---|---|---|
field |
Simple field | name , email |
object.field |
Nested field | user.name , settings.theme |
array.*.field |
Array elements | users.*.email |
object.array.*.field |
Nested array | dept.users.*.name |
array.*.object.field |
Array of objects | users.*.profile.age |
array.*.nested.*.field |
Deeply nested | groups.*.users.*.email |
π€ Author
Firuz Vorisov
github.com/vfiruz97
Feel free to open issues or contribute via PR!
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License.
Libraries
- input_validate
- A Dart package for validating nested map data structures.