NAME
Params::Filter - Fast field filtering for parameter construction
SYNOPSIS
use Params::Filter;
# Define filter rules
my @required_fields = qw(name email);
my @accepted_fields = qw(phone city state zip);
my @excluded_fields = qw(ssn password);
# Functional interface
# Apply filter to incoming data (from web form, CLI, API, etc.)
my ($filtered_data, $status) = filter(
$incoming_params, # Data from external source
\@required_fields,
\@accepted_fields,
\@excluded_fields,
);
if ($filtered_data) {
# Success - use filtered data
process_user($filtered_data);
} else {
# Error - missing required fields
die "Validation failed: $status";
}
# Object-oriented interface
my $user_filter = Params::Filter->new_filter({
required => ['username', 'email'],
accepted => ['first_name', 'last_name', 'phone', 'bio'],
excluded => ['password', 'ssn', 'credit_card'],
});
# Apply same filter to multiple incoming datasets
my ($user1, $msg1) = $user_filter->apply($web_form_data);
my ($user2, $msg2) = $user_filter->apply($api_request_data);
my ($user3, $msg3) = $user_filter->apply($db_record_data);
DESCRIPTION
Params::Filter provides fast, lightweight parameter filtering that checks only for the presence or absence of specified fields. It does not validate values - no type checking, truthiness testing, or lookups.
This module separates field filtering from value validation:
**Field filtering** (this module) - Check which fields are present/absent
**Value validation** (later step) - Check if field values are correct
This approach handles common parameter issues:
Subroutine signatures can become unwieldy with many parameters
Ad-hoc argument checking is error-prone
Validation may not catch missing inputs quickly enough
The number of fields to check multiplies validation time
When to Use This Module
This module is useful when you have:
Pre-defined filter rules (from config files, constants, database schemas)
Known downstream input or process parameters (for APIs, method/subroutine arguments, database operations)
Incoming data from differing sources (web forms, APIs, databases, user input)
No guarantee that incoming data is consistent or complete
Need to process multiple datasets with the same rules
Want to reject unwanted fields before value validation
When NOT to Use This Module
If you're constructing both the filter rules and the data structure at the same point in your code, you probably don't need this module except during development or debugging. The module's expected use is to apply pre-defined rules to data that may be inconsistent or incomplete for its intended use. If there isn't repetition or an unreliable data structure, this might be overkill.
This Module Does NOT Do Fancy Stuff
As much as this module attempts to be versatile in usage, there are some very handy affordances it does NOT provide:
No regex field name matching for designating fields to require, accept, or exclude
No conditional field designations within a filter:
if 'mailing_address' require 'postal_code'. But seeset_required(),set_accepted(),set_excluded(), as ways to adjust a filter's behavior - or just have alternative filters.No coderefs or callbacks for use when filtering
No substitutions or changes to field names
No built-in filter lists except null [] = none
No fields added to data, EXCEPT:
* If the provided data resolves to a list or array with an odd number of elements, the LAST element is treated as a flag, set to the value 1
* If the provided data resolves to a single non-reference scalar (probably a text string) the data is returned as a hashref value with the key ‘_’
OBJECT-ORIENTED INTERFACE
new_filter
my $filter = Params::Filter->new_filter({
required => ['field1', 'field2'],
accepted => ['field3', 'field4', 'field5'],
excluded => ['forbidden_field'],
DEBUG => 1, # Optional debug mode
});
# Empty constructor - rejects all fields by default
my $strict_filter = Params::Filter->new_filter();
Creates a reusable filter object with predefined field rules. The filter can then be applied to multiple datasets using the "apply" method.
Parameters
required- Arrayref of names of required fields (default: [])accepted- Arrayref of names of optional fields (default: [])excluded- Arrayref of names of fields to always remove (default: [])DEBUG- Boolean to enable debug warnings (default: 0)
Returns
A Params::Filter object
Example
# Create filter for user registration data
my $user_filter = Params::Filter->new_filter({
required => ['username', 'email'],
accepted => ['first_name', 'last_name', 'phone', 'bio'],
excluded => ['password', 'ssn', 'credit_card'],
});
# Apply to multiple incoming datasets
my ($user1, $msg1) = $user_filter->apply($web_form_data);
my ($user2, $msg2) = $user_filter->apply($api_request_data);
apply
my ($filtered, $status) = $filter->apply($input_data);
Applies the filter's predefined rules to input data. This is the OO equivalent of the "filter" function.
Parameters
$input_data- Hashref, arrayref, or scalar to filter
Returns
In list context: (hashref, status_message) or (undef, error_message)
In scalar context: Hashref with filtered parameters, or undef on failure
Example
my $filter = Params::Filter->new_filter({
required => ['id', 'type'],
accepted => ['name', 'value'],
});
# Process multiple records from database
for my $record (@db_records) {
my ($filtered, $msg) = $filter->apply($record);
if ($filtered) {
process_record($filtered);
} else {
log_error("Record failed: $msg");
}
}
MODIFIER METHODS
Modifier methods allow dynamic configuration of filter rules after creation of the filter object. All methods return $self for method chaining. A filter may call its modifier methods more than once, and the changes take effect immediately.
set_required
$filter->set_required(['id', 'name', 'email']); # Arrayref
$filter->set_required('id', 'name', 'email'); # List
$filter->set_required(); # Clears to []
Sets the required field names. Accepts either an arrayref or a list of field names. Calling with no arguments sets required to empty array.
set_accepted
$filter->set_accepted(['phone', 'city']); # Arrayref
$filter->set_accepted('phone', 'city'); # List
$filter->set_accepted(); # Clears to []
$filter->set_accepted(['*']); # Accept all (except excluded)
Sets the optional (accepted) field names. Accepts either an arrayref or a list of field names. Calling with no arguments sets accepted to empty array.
set_excluded
$filter->set_excluded(['password', 'ssn']); # Arrayref
$filter->set_excluded('password', 'ssn'); # List
$filter->set_excluded(); # Clears to []
Sets the excluded field names (fields to always remove). Accepts either an arrayref or a list of field names. Calling with no arguments sets excluded to empty array.
accept_all
$filter->accept_all(); # Sets accepted to ['*']
Convenience method that sets accepted fields to ['*'] (wildcard mode), allowing all fields except those in excluded.
accept_none
$filter->accept_none(); # Sets accepted to []
Convenience method that sets accepted fields to [] (empty array), allowing only required fields.
Modifier Method Examples
# Method chaining for one-liner configuration
my $filter = Params::Filter->new_filter();
# When needed:
$filter->set_required(['id', 'name'])
->set_accepted(['email', 'phone'])
->set_excluded(['password']);
# Environment-based configuration
my $filter = Params::Filter->new_filter();
if ($ENV{MODE} eq 'production') {
$filter->set_required(['api_key'])
->set_accepted(['timeout', 'retries'])
->set_excluded(['debug_info']);
} else {
$filter->set_required(['debug_mode'])
->accept_all();
}
# Dynamic configuration from config file
if ( $DEBUG ) {
my $db_config = load_config('debug_fields.json');
$filter->set_required($db_config->{required})
->set_accepted($db_config->{accepted})
->set_excluded($db_config->{excluded});
}
FUNCTIONAL INTERFACE
filter
my ($filtered, $status) = filter(
$input_data, # Hashref, arrayref, or scalar
\@required, # Arrayref of required field names
\@accepted, # Arrayref of optional field names (default: [])
\@excluded, # Arrayref of names of fields to remove (default: [])
$debug_mode, # Boolean: enable warnings (default: 0)
);
# Scalar context - returns filtered hashref or undef on failure
my $result = filter($input, \@required, \@accepted);
Filters input data according to field specifications. Only checks for presence/absence of fields, not field values.
Parameters
$input_data- Input parameters (hashref, arrayref, or scalar)\@required- Arrayref of names of fields that must be present\@accepted- Arrayref of optional names of fields to accept (default: [])\@excluded- Arrayref of names of fields to remove even if accepted (default: [])$debug_mode- Boolean to enable warnings (default: 0)
Returns
In list context: (hashref, status_message) or (undef, error_message)
In scalar context: Hashref with filtered parameters, or undef on failure
Example
# Define filter rules (could be from config file)
my @required = qw(username email);
my @accepted = qw(full_name phone);
my @excluded = qw(password ssn);
# Apply to incoming data from web form
my ($user_data, $msg) = filter(
$form_submission,
\@required,
\@accepted,
\@excluded,
);
if ($user_data) {
create_user($user_data);
} else {
log_error($msg);
}
RETURN VALUES
Both "filter" and "apply" return different values depending on context:
Success
List context:
(hashref, "Admitted")or(hashref, warning_message)Scalar context: Hashref with filtered parameters
Failure
List context:
(undef, error_message)Scalar context:
undef
Common Status Messages
"Admitted" - All required fields present, filtering successful
"Plain text argument accepted with key '_': '...'" - Parsing message (always shown)
"Odd number of arguments provided; last element 'X' converted to flag with value 1" - Parsing message (always shown)
"Ignoring excluded arguments: 'field1', 'field2'..." - Debug message (debug mode only)
"Ignoring unrecognized arguments: 'field1', 'field2'..." - Debug message (debug mode only)
"Unable to initialize without required arguments: 'field1', 'field2'..." - Error
FEATURES
**Dual interface** - Functional or OO usage
**Fast-fail** - Returns immediately on missing required parameters
**Fast-success** - Returns immediately if all required parameters are provided and no others are provided or will be accepted
**Flexible input** - Accepts hashrefs, arrayrefs, or scalars
**Wildcard support** - Use
'*'in accepted list to accept all fields**No value checking** - Only presence/absence of fields
**Debug mode** - Optional warnings about unrecognized or excluded fields
**Method chaining** - Modifier methods return
$self**Perl 5.40+** - Modern Perl with signatures and post-deref
**No dependencies** - Only core Perl's Exporter
DEBUG MODE
Debug mode provides additional information about field filtering during development:
my ($filtered, $msg) = filter(
$input,
['name'],
['email'],
['password'],
1, # Enable debug mode
);
Debug warnings (only shown when debug mode is enabled):
Excluded fields that were removed
Unrecognized fields that were ignored
Parsing messages (always shown, regardless of debug mode):
Plain text arguments accepted with key '_'
Odd number of array elements converted to flags
Parsing messages inform you about transformations the filter made to your input format. These are always reported because they affect the structure of the returned data. Debug warnings help you understand which fields were filtered out during development.
WILDCARD SUPPORT
The accepted parameter supports a wildcard '*' to accept all fields (except those in excluded).
Wildcard Usage
# Accept all fields
filter($input, [], ['*']);
# Accept all except specific exclusions
filter($input, [], ['*'], ['password', 'ssn']);
# Required + all other fields
filter($input, ['id', 'name'], ['*']);
Important Notes
'*'is only special in theacceptedparameterIn
requiredorexcluded,'*'is treated as a literal field nameEmpty
[]for accepted means "accept none beyond required"Multiple wildcards are redundant but harmless
Exclusions are always removed before acceptance is processed
Debugging Pattern
A common debugging pattern is to add '*' to an existing accepted list:
# Normal operation
filter($input, ['id'], ['name', 'email']);
# Debugging - see all inputs
filter($input, ['id'], ['name', 'email', '*']);
Or, start with minimum to troubleshoot specific fields
filter($input, ['id'], []);
# then
filter($input, ['id'], ['name']);
# then
filter($input, ['id'], ['email']);
# then
filter($input, ['id'], ['name', 'email']);
# then
filter($input, ['id'], ['*']);
EXAMPLES
Basic Form Validation
use Params::Filter; # auto-imports filter() subroutine
# Define filtering rules (could be from config file)
my @required = qw(name email);
my @accepted = qw(phone city state zip);
# Apply to incoming web form data
my ($user_data, $status) = filter(
$form_submission, # Data from web form
\@required,
\@accepted,
);
if ($user_data) {
register_user($user_data);
} else {
show_error($status);
}
Reusable Filter for Multiple Data Sources
# Create filter once
my $user_filter = Params::Filter->new_filter({
required => ['username', 'email'],
accepted => ['full_name', 'phone', 'bio'],
excluded => ['password', 'ssn', 'credit_card'],
});
# Apply to multiple incoming datasets
my ($user1, $msg1) = $user_filter->apply($web_form_data);
my ($user2, $msg2) = $user_filter->apply($api_request_data);
my ($user3, $msg3) = $user_filter->apply($csv_import_data);
Environment-Specific Filtering
my $filter = Params::Filter->new_filter();
if ($ENV{APP_MODE} eq 'production') {
# Strict: only specific fields allowed
$filter->set_required(['api_key'])
->set_accepted(['timeout', 'retries'])
->set_excluded(['debug_info', 'verbose']);
} else {
# Development: allow everything
$filter->set_required(['debug_mode'])
->accept_all();
}
my ($config, $msg) = $filter->apply($incoming_config);
Security Filtering
# Remove sensitive fields from user input
my ($safe_data, $msg) = filter(
$user_input,
['username', 'email'], # required
['full_name', 'phone', 'bio'], # accepted
['password', 'ssn', 'api_key'], # excluded
);
# Result contains only safe fields
# password, ssn, api_key are removed even if provided
Dynamic Configuration from File
# Load filter rules from config file
my $config = decode_json(`cat filters.json`);
my $filter = Params::Filter->new_filter()
->set_required($config->{user_create}{required})
->set_accepted($config->{user_create}{accepted})
->set_excluded($config->{user_create}{excluded});
# Apply to incoming data
my ($filtered, $msg) = $filter->apply($api_data);
Data Segregation for Multiple Subsystems
A common pattern is splitting incoming data into subsets for different handlers or storage locations. Each filter extracts only the fields needed for its specific purpose, implementing security through compartmentalization.
# Main subscription form collects:
# name, email, zip,
# user_id, password, credit_card_number, subscription_term
# Subscriber profile form collects:
# name, email, address, city, state, zip,
# user_id, password, credit_card_number,
# phone, occupation, position, education
# alt_card_number, billing_address, billing_zip
# Promo subscription form collects:
# name, email, zip, subscription_term,
# user_id, password, credit_card_number, promo_code
my $data = $webform->input(); # From any of the above
# Filters
# Personal data - general user info (no sensitive data)
my $person_filter = Params::Filter->new_filter({
required => ['name', 'user_id', 'email'],
accepted => ['address', 'city', 'state', 'zip', 'phone', 'occupation', 'position', 'education'],
excluded => ['password', 'credit_card_number'],
});
# Business data - subscription and billing info
my $biz_filter = Params::Filter->new_filter({
required => ['user_id', 'subscription_term', 'credit_card_number', 'zip'],
accepted => ['alt_card_number', 'billing_address', 'billing_zip', 'promo_code'],
excluded => ['password'],
});
# Authentication data - only credentials
my $auth_filter = Params::Filter->new_filter({
required => ['user_id', 'password'],
accepted => [],
excluded => [],
});
# Apply all filters to the same web form submission
my ($person_data, $pmsg) = $person_filter->apply($data);
my ($biz_data, $bmsg) = $biz_filter->apply($data);
my ($auth_data, $amsg) = $auth_filter->apply($data);
unless ($person_data && $biz_data && $auth_data) {
return "Unable to add user: " .
join ' ' => grep { $_ ne 'Admitted' } ($pmsg, $bmsg, $amsg);
}
# Collect any debug warnings from successful filters
if ($self->{DEBUG}) {
my @warnings = grep { $_ ne 'Admitted' } ($pmsg, $bmsg, $amsg);
warn "Params filter debug warnings:\n" . join("\n", @warnings) . "\n"
if @warnings;
}
# Route each subset to appropriate handler
$self->add_user($person_data); # User profile
$self->set_subscription($biz_data); # Billing system
$self->set_password($auth_data); # Auth system
# continue ...
B<Note>: The original C<$data> is not modified by any filter. Each call to
C<apply()> creates its own internal copy, so the same data can be safely
processed by multiple filters.
SEE ALSO
Params::Validate - Full-featured parameter validation
Data::Verifier - Data structure validation
JSON::Schema::Modern - JSON Schema validation
AUTHOR
Bruce Van Allen <bva@cruzio.com>
LICENSE
This module is licensed under the same terms as Perl itself. See perlartistic.
COPYRIGHT
Copyright (C) 2026, Bruce Van Allen
3 POD Errors
The following errors were encountered while parsing the POD:
- Around line 130:
Unknown directive: =over8
- Around line 136:
Non-ASCII character seen before =encoding in '‘_’'. Assuming UTF-8
- Around line 140:
=back without =over