Overview
Array operators enable checking if a value exists in a collection. Rule Engine JS provides two array membership operators:
in Check if value exists in array
notIn Check if value does NOT exist in array
Architecture
Array operators are implemented in the ArrayOperators class extending BaseOperator:
Source Files:
Array operators: src/operators/array.js
Base operator: src/operators/base/BaseOperator.js
Type utilities: src/utils/TypeUtils.js
Unit tests: tests/unit/operators/array.test.js
Key Features
Dynamic arrays - Array can be a field path or literal value
Dynamic values - Value to check can also be a field path
Strict/loose comparison - Control type coercion behavior
Case sensitive - Exact matching for strings by default
Comprehensive validation - Validates right operand is an array
in - Array Membership
Check if a value exists in an array.
Syntax
{ in : [ value , array ] }
{ in : [ value , array , options ] }
Parameters
The value to search for - field path or literal value
The array to search in - field path or literal array
Configuration options Enable strict equality checking (===) instead of loose (==)
Returns
boolean - true if value exists in array, false otherwise
Examples
Basic Membership
Dynamic Arrays
Number Arrays
Case Sensitivity
With Rule Helpers
import { createRuleEngine } from 'rule-engine-js' ;
const engine = createRuleEngine ();
const user = {
role: 'admin' ,
tags: [ 'premium' , 'verified' , 'admin' ],
permissions: [ 'read' , 'write' , 'delete' ]
};
// Check if role is in allowed list
engine . evaluateExpr (
{ in: [ 'role' , [ 'admin' , 'moderator' , 'user' ]] },
user
);
// Result: { success: true }
// Check if tag exists
engine . evaluateExpr (
{ in: [ 'premium' , 'tags' ] },
user
);
// Result: { success: true }
// Check permission
engine . evaluateExpr (
{ in: [ 'write' , 'permissions' ] },
user
);
// Result: { success: true }
// Value not in array
engine . evaluateExpr (
{ in: [ 'role' , [ 'guest' , 'viewer' ]] },
user
);
// Result: { success: false }
Common Use Cases
Role-Based Access Control
const user = {
role: 'editor' ,
department: 'marketing'
};
// Allow multiple roles
const canAccessAdmin = {
in: [ 'role' , [ 'admin' , 'superadmin' , 'owner' ]]
};
// Department-based access
const canAccessReports = {
in: [ 'department' , [ 'marketing' , 'sales' , 'analytics' ]]
};
engine . evaluateExpr ( canAccessAdmin , user );
// Result: { success: false }
engine . evaluateExpr ( canAccessReports , user );
// Result: { success: true }
const user = {
id: 'user123' ,
permissions: [ 'posts:read' , 'posts:write' , 'comments:read' ]
};
// Check specific permission
const canWritePosts = {
in: [ 'posts:write' , 'permissions' ]
};
// Check multiple permissions
const canManagePosts = {
and: [
{ in: [ 'posts:read' , 'permissions' ] },
{ in: [ 'posts:write' , 'permissions' ] },
{ in: [ 'posts:delete' , 'permissions' ] } // This will fail
]
};
engine . evaluateExpr ( canWritePosts , user );
// Result: { success: true }
engine . evaluateExpr ( canManagePosts , user );
// Result: { success: false } - missing delete permission
const user = {
email: 'beta@company.com' ,
features: [ 'dark-mode' , 'new-editor' , 'beta-search' ]
};
// Check if feature is enabled
const hasNewEditor = {
in: [ 'new-editor' , 'features' ]
};
// Multiple feature check
const hasBetaAccess = {
or: [
{ in: [ 'beta-search' , 'features' ] },
{ in: [ 'beta-analytics' , 'features' ] }
]
};
engine . evaluateExpr ( hasNewEditor , user );
// Result: { success: true }
engine . evaluateExpr ( hasBetaAccess , user );
// Result: { success: true }
const article = {
title: 'Introduction to React' ,
tags: [ 'react' , 'javascript' , 'frontend' , 'tutorial' ]
};
// Search by tag
const isReactArticle = {
in: [ 'react' , 'tags' ]
};
// Multiple tag filter
const isFrontendTutorial = {
and: [
{ in: [ 'frontend' , 'tags' ] },
{ in: [ 'tutorial' , 'tags' ] }
]
};
engine . evaluateExpr ( isReactArticle , article );
// Result: { success: true }
engine . evaluateExpr ( isFrontendTutorial , article );
// Result: { success: true }
const order = {
status: 'processing' ,
validStatuses: [ 'pending' , 'processing' , 'shipped' , 'delivered' ]
};
// Validate status is in allowed list
const validStatus = {
in: [ 'status' , 'validStatuses' ]
};
engine . evaluateExpr ( validStatus , order );
// Result: { success: true }
// Check for specific statuses
const canCancel = {
in: [ 'status' , [ 'pending' , 'processing' ]]
};
engine . evaluateExpr ( canCancel , order );
// Result: { success: true }
Performance : Array membership uses optimized Array.some() with early exit for better performance.
notIn - Array Exclusion
Check if a value does NOT exist in an array (inverse of in).
Syntax
{ notIn : [ value , array ] }
{ notIn : [ value , array , options ] }
Parameters
The value to check for exclusion - field path or literal value
The array to check against - field path or literal array
Configuration options Enable strict equality checking (===) instead of loose (==)
Returns
boolean - true if value does NOT exist in array, false if it exists
Examples
Basic Exclusion
Account Validation
Security Checks
Content Filtering
Dynamic Exclusion
const user = {
role: 'admin' ,
status: 'active' ,
tags: [ 'premium' , 'verified' ]
};
// Ensure role is not in banned list
engine . evaluateExpr (
{ notIn: [ 'role' , [ 'guest' , 'banned' , 'suspended' ]] },
user
);
// Result: { success: true }
// Check tag is not present
engine . evaluateExpr (
{ notIn: [ 'spam' , 'tags' ] },
user
);
// Result: { success: true }
// This will fail (admin is in the list)
engine . evaluateExpr (
{ notIn: [ 'role' , [ 'admin' , 'moderator' ]] },
user
);
// Result: { success: false }
Common Use Cases
const user = {
email: 'user@example.com' ,
ip: '192.168.1.100' ,
blockedEmails: [ 'spam@test.com' , 'fake@test.com' ],
blockedIPs: [ '10.0.0.1' , '192.168.1.50' ]
};
// Ensure user is not blacklisted
const notBlacklisted = {
and: [
{ notIn: [ 'email' , 'blockedEmails' ] },
{ notIn: [ 'ip' , 'blockedIPs' ] }
]
};
engine . evaluateExpr ( notBlacklisted , user );
// Result: { success: true }
const order = {
status: 'processing' ,
completedStatuses: [ 'shipped' , 'delivered' , 'cancelled' ]
};
// Can still modify order (not in completed statuses)
const canModify = {
notIn: [ 'status' , 'completedStatuses' ]
};
engine . evaluateExpr ( canModify , order );
// Result: { success: true }
// Order not in these states
const notFinal = {
notIn: [ 'status' , [ 'cancelled' , 'refunded' ]]
};
engine . evaluateExpr ( notFinal , order );
// Result: { success: true }
const upload = {
filename: 'document.pdf' ,
extension: '.pdf' ,
blockedExtensions: [ '.exe' , '.bat' , '.sh' , '.cmd' ]
};
// Ensure file type is allowed
const safeFileType = {
notIn: [ 'extension' , 'blockedExtensions' ]
};
engine . evaluateExpr ( safeFileType , upload );
// Result: { success: true }
For checking if value is NOT in a literal array of 2-3 items, notIn is clearer than multiple neq operators combined with and.
Error Handling
Common Errors
const data = {
user: {
role: 'admin' ,
name: 'John Doe' // Not an array
}
};
// Trying to use IN with non-array
const result = engine . evaluateExpr (
{ in: [ 'admin' , 'user.name' ] },
data
);
// Returns:
// {
// success: false,
// error: "IN operator requires array as right operand",
// details: {
// rightType: "string",
// originalRight: "user.name"
// }
// }
const data = {
user: { role: 'admin' }
// Missing 'permissions' field
};
// Field doesn't exist
const result = engine . evaluateExpr (
{ in: [ 'read' , 'user.permissions' ] },
data
);
// Returns:
// {
// success: false,
// error: "IN operator requires array as right operand"
// }
// Missing second argument
const result = engine . evaluateExpr (
{ in: [ 'admin' ] },
data
);
// Returns:
// {
// success: false,
// error: "IN operator requires 2-3 arguments, got 1"
// }
Literal String Instead of Array
const data = { role: 'admin' };
// Passing string instead of array
const result = engine . evaluateExpr (
{ in: [ 'role' , 'admin,moderator' ] }, // Wrong: string not array
data
);
// Returns:
// {
// success: false,
// error: "IN operator requires array as right operand"
// }
// Correct usage:
const correctResult = engine . evaluateExpr (
{ in: [ 'role' , [ 'admin' , 'moderator' ]] }, // Correct: array
data
);
// Result: { success: true }
Safe Error Handling
function safeArrayCheck ( engine , operator , value , array , data , fallback = false ) {
// Validate array field exists and is array
const resolvedArray = engine . resolvePath ( data , array );
if ( ! Array . isArray ( resolvedArray )) {
console . error ( `Field ' ${ array } ' is not an array` );
return fallback ;
}
const result = engine . evaluateExpr (
{ [operator]: [ value , array ] },
data
);
if ( ! result . success ) {
console . error ( `Array check failed: ${ result . error } ` );
return fallback ;
}
return result . success ;
}
// Usage
const hasPermission = safeArrayCheck (
engine ,
'in' ,
'write' ,
'user.permissions' ,
userData ,
false
);
Type Coercion & Comparison
Array operators use TypeUtils.isEqual() for element comparison:
Loose Mode (Default)
const data = {
numbers: [ 1 , 2 , 3 ],
strings: [ '1' , '2' , '3' ]
};
// Loose equality - type coercion enabled
engine . evaluateExpr ({ in: [ 1 , 'strings' ] }, data );
// Result: { success: true } - 1 == '1'
engine . evaluateExpr ({ in: [ '2' , 'numbers' ] }, data );
// Result: { success: true } - '2' == 2
Strict Mode
const strictEngine = createRuleEngine ({ strict: true });
// Strict equality - no type coercion
strictEngine . evaluateExpr ({ in: [ 1 , 'strings' ] }, data );
// Result: { success: false } - 1 !== '1'
strictEngine . evaluateExpr ({ in: [ '2' , 'numbers' ] }, data );
// Result: { success: false } - '2' !== 2
// Per-rule strict mode
engine . evaluateExpr (
{ in: [ 1 , 'strings' , { strict: true }] },
data
);
// Result: { success: false }
Comparison Table
Value Type Array Element Loose Mode Strict Mode
11✅ Match ✅ Match 1'1'✅ Match ❌ No match 'true'true❌ No match ❌ No match nullundefined❌ No match ❌ No match
Use strict mode in production to prevent unexpected type coercion bugs when checking array membership.
API Reference
For complete API documentation: