A lightweight, powerful object search engine for JavaScript with advanced query syntax.
Import using NPM:
npm install @ecromaneli/search-engineor download the latest release version and import it manually:
<script src="search-engine.min.js"></script>No Dependencies. Works in Node.js and browsers.
- Powerful query language with boolean operators (AND/OR)
- Field-specific searches
- Support for wildcards and regex pattern matching
- Numeric range searches
- Logical negation of search terms
- Nested property searching
- Logical grouping with parentheses
- Fuzzy (typo-tolerant) search with optional relevance sorting
- Customizable search options
- Zero dependencies
const SearchEngine = require('@ecromaneli/search-engine')
const users = [
{ id: 1, name: 'John Doe', age: 28, tags: ['developer', 'javascript'] },
{ id: 2, name: 'Jane Smith', age: 34, tags: ['designer', 'ui/ux'] },
{ id: 3, name: 'Bob Johnson', age: 45, tags: ['manager', 'finance'] }
]
const result = SearchEngine.search(users, 'name: john')
console.log(result)You can create a SearchEngine instance with specific options that will be used for all searches:
const engine = new SearchEngine({
excludeKeys: ['name', 'tags'],
allowNumericString: false,
allowKeyValueMatching: true,
matchChildKeysAsValues: false,
maxLevels: 5
})
const results = engine.search(users, 'age~: 25-35')
console.log(results)The SearchOptions object allows you to customize the behavior of the search engine. Below is a table explaining each option:
| Option | Type | Default | Description |
|---|---|---|---|
excludeKeys |
string[] |
[] |
Array of keys to exclude from search. Useful for excluding sensitive data. |
allowNumericString |
boolean |
true |
Controls whether string values that can be parsed as numbers are used in range searches. |
allowKeyValueMatching |
boolean |
true |
When enabled, unquoted terms without a field/value separator match both field names and values. |
matchChildKeysAsValues |
boolean |
false |
When enabled, after finding a matching key, also looks for the value in child object keys. |
maxLevels |
number |
unlimited | Maximum levels of nested objects to search through. Useful to avoid infinite loops in deeply nested or circular data. |
fuzzy |
boolean | object |
false |
Enables typo-tolerant matching of string values. See Fuzzy Search. |
- The
maxLevelsoption can be set to limit how deep the search will go into nested objects. This is especially useful for large or circular data structures.
-
allowNumericString:- When
true, strings like"123"will be treated as numbers in range searches. - When
false, only actual numbers will be considered for range searches.
- When
-
allowKeyValueMatching:- When
true, a query likeadminwill match both{ admin: "value" }and{ key: "admin" }. - When
false, it will only match field names or values explicitly.
- When
-
matchChildKeysAsValues:- When
true, a query likefoo:barwill match both{ foo: "bar" }and{foo: { bar: "value" }}. - When
false, it will only match{ foo: "bar" }.
- When
Fuzzy search is disabled by default. Set fuzzy: true to enable it with the default settings, or pass an object to customize it:
SearchEngine.search(users, 'name: jhon', { fuzzy: true })
// => [{ id: 1, name: 'John Doe', ... }, { id: 3, name: 'Bob Johnson', ... }]
const engine = new SearchEngine({
fuzzy: {
algorithm: 'damerau',
tolerance: 0.25,
maxDistance: 2,
minLength: 3,
sort: true
}
})
engine.search(users, 'name: jonh or tags: desinger')| Option | Type | Default | Description |
|---|---|---|---|
algorithm |
string |
'damerau' |
Algorithm used to compare the terms with the values. See the list below. |
tolerance |
number |
0.25 |
Fraction (0 to 1) of the term length that can be edited. E.g. 0.25 allows 1 edit for terms with 4 to 7 characters. |
maxDistance |
number |
unlimited | Maximum number of edits allowed regardless of the term length. |
minLength |
number |
3 |
Terms shorter than this are matched exactly. |
sort |
boolean |
false |
Sorts the results by relevance (closest matches first). Ties keep the original order. |
damerau: Finds the term inside the value allowing insertions, deletions, substitutions and swaps of adjacent characters (each counts as 1 edit). Best for typos (jhon→John).levenshtein: Same asdamerau, but swapping two characters counts as 2 edits.subsequence: The characters of the term must appear in the value in the same order, gaps allowed (jsmth→John Smith). Best for abbreviations.toleranceandmaxDistanceare ignored.
The number of allowed edits is min(maxDistance, floor(term length × tolerance)).
- Only plain value matches (
field: value,"value"and bare terms likejhon) are fuzzy. - Field names, regex (
*:), range (~:) andis/is notqueries are always exact. - Values of type
numberare always exact (age: 31does not match30), while numeric strings are fuzzy (zip: 10002matches"10001"). - Exact matches are always checked first, so they are not slowed down.
When sort is true, each condition that is not negated adds a score from 0 to 1 to the object (1 for an exact match, less for each edit or gap). Objects are sorted by their total score, so objects that match more conditions, or match them more closely, come first.
foo- Search for objects having a field or valuefoo."value"- Search for"value"in any field.field: value- Search forvaluein the specificfield.
field*: pattern- Regex pattern match on field.field~: min-max- Numeric range search.field~: 10-- Numbers greater than or equal to 10.field~: -20- Numbers less than or equal to 20.
field is value- Search for objects wherefieldis exactlyvalue.field is not value- Search for objects wherefieldis notvalue.
Where value must be one of the following:
- A boolean (
trueorfalse) nullundefined(orundef)blank(empty strings)empty(empty arrays)
term1 and term2- Both terms must match.term1 or term2- Either term must match.
not term- Term must not match.not (term1 or term2)- Group negation (neither term1 nor term2 should match).(term1 or term2) and term3- Logical grouping with parentheses.
objList(Array): Array of objects to search through.queryStr(String): Query string following the syntax described above.options(Object, optional): Search options object.- Returns (
Array): Array of matching objects.
To store the options, use the constructor below:
options(Object, optional): Search options object (see Options table).- Returns (
SearchEngine): A newSearchEngineinstance with the specified options.
objList(Array): Array of objects to search through.queryStr(String): Query string following the syntax described above.- Returns (
Array): Array of matching objects with the instance's options applied.
- For large datasets, consider disabling
allowNumericStringif you don't need string-to-number conversion. - Set
matchChildKeysAsValues: false(default) unless you specifically need to match object keys as values. - Use
excludeKeysto skip searching in fields that are never relevant to your searches. - For repeated searches with the same options, create a
SearchEngineinstance instead of using the static method. - Limit
maxLevelsif you have deeply nested data to avoid performance issues. - Fuzzy search only runs when an exact match fails, but it may still make searches noticeably slower on large datasets. Use
minLengthandmaxDistanceto limit its cost, and enablesortonly when needed.
For a comprehensive set of usage examples covering all search engine features, refer to our test suite:
The test file includes examples of:
- Complex nested property searching
- Array item searching
- Logical grouping with parentheses
- De Morgan's law transformations
- Complex boolean expressions
- Excluded keys functionality
- Range searches with various configurations
- Quoted values with special characters
- Edge cases and error handling
These examples serve as excellent reference implementations when building advanced search queries.
Created by Emerson Capuchi Romaneli (@ECRomaneli).
This project is licensed under the MIT License.