Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
match-sorter
Advanced tools
Simple, expected, and deterministic best-match sorting of an array in JavaScript
The match-sorter npm package is a simple, expected, and deterministic best-match sorting of an array in JavaScript. It's used to filter and sort lists of strings or objects by best match against a given query.
Simple string matching
This feature allows you to sort an array of strings by matching against a single query string. It returns the items that match the query, ordered by best match.
import matchSorter from 'match-sorter';
const fruits = ['apple', 'orange', 'grape', 'banana'];
const sortedFruits = matchSorter(fruits, 'ap');
// sortedFruits will be ['apple', 'grape']
Object array matching
This feature allows you to sort an array of objects by matching against a query string on specified object keys. It returns the objects that match the query, ordered by best match.
import matchSorter from 'match-sorter';
const items = [{name: 'apple'}, {name: 'orange'}, {name: 'grape'}, {name: 'banana'}];
const sortedItems = matchSorter(items, 'ap', {keys: ['name']});
// sortedItems will be [{name: 'apple'}, {name: 'grape'}]
Custom ranking strategies
This feature allows you to customize the ranking strategy used for sorting. You can specify the order of ranking methods to tailor the sorting to your needs.
import matchSorter, {rankings} from 'match-sorter';
const books = ['The Hobbit', 'Game of Thrones', 'Curious George'];
const sortedBooks = matchSorter(books, 'th', {rankings: [rankings.STARTS_WITH, rankings.EQUAL, rankings.CONTAINS]});
// sortedBooks will be ['The Hobbit', 'Game of Thrones']
Fuse.js is a powerful, lightweight fuzzy-search library with a similar goal to match-sorter. It provides flexible searching with customizable search options, but it might be more complex to use for simple use cases compared to match-sorter.
Fuzzy is a JavaScript module for fuzzy string matching. It's useful for filtering large arrays of strings or objects by a search term. It's less feature-rich and has a simpler API than match-sorter, which might be preferable for basic use cases.
Quick-score is a library for scoring and sorting string matches for autocomplete suggestions. It's optimized for speed and is a good alternative to match-sorter when performance is a critical factor.
Simple, expected, and deterministic best-match sorting of an array in JavaScript
This follows a simple and sensible (user friendly) algorithm that makes it easy for you to filter and sort a list of items based on given input. Items are ranked based on sensible criteria that result in a better user experience.
To explain the ranking system, I'll use countries as an example:
France
would match France
, but not france
)France
would match france
)Sou
would
match South Korea
or South Africa
)Repub
would match
Dominican Republic
)ham
would match
Bahamas
)us
would match
United States
)iw
would match Zimbabwe
, but not Kuwait
because it must be in the same order). Furthermore, if the item is a closer
match, it will rank higher (ex. ua
matches Uruguay
more closely than
United States of America
, therefore Uruguay
will be ordered before
United States of America
)This ranking seems to make sense in people's minds. At least it does in mine. Feedback welcome!
This module is distributed via npm which is bundled with node and
should be installed as one of your project's dependencies
:
npm install match-sorter
import {matchSorter} from 'match-sorter'
// or const {matchSorter} = require('match-sorter')
// or window.matchSorter.matchSorter
const list = ['hi', 'hey', 'hello', 'sup', 'yo']
matchSorter(list, 'h') // ['hello', 'hey', 'hi']
matchSorter(list, 'y') // ['yo', 'hey']
matchSorter(list, 'z') // []
[string]
Default: undefined
By default it just uses the value itself as above. Passing an array tells match-sorter which keys to use for the ranking.
const objList = [
{name: 'Janice', color: 'Green'},
{name: 'Fred', color: 'Orange'},
{name: 'George', color: 'Blue'},
{name: 'Jen', color: 'Red'},
]
matchSorter(objList, 'g', {keys: ['name', 'color']})
// [{name: 'George', color: 'Blue'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}]
matchSorter(objList, 're', {keys: ['color', 'name']})
// [{name: 'Jen', color: 'Red'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}, {name: 'George', color: 'Blue'}]
Array of values: When the specified key matches an array of values, the best match from the values of in the array is going to be used for the ranking.
const iceCreamYum = [
{favoriteIceCream: ['mint', 'chocolate']},
{favoriteIceCream: ['candy cane', 'brownie']},
{favoriteIceCream: ['birthday cake', 'rocky road', 'strawberry']},
]
matchSorter(iceCreamYum, 'cc', {keys: ['favoriteIceCream']})
// [{favoriteIceCream: ['candy cane', 'brownie']}, {favoriteIceCream: ['mint', 'chocolate']}]
Nested Keys: You can specify nested keys using dot-notation.
const nestedObjList = [
{name: {first: 'Janice'}},
{name: {first: 'Fred'}},
{name: {first: 'George'}},
{name: {first: 'Jen'}},
]
matchSorter(nestedObjList, 'j', {keys: ['name.first']})
// [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}]
const nestedObjList = [
{name: [{first: 'Janice'}]},
{name: [{first: 'Fred'}]},
{name: [{first: 'George'}]},
{name: [{first: 'Jen'}]},
]
matchSorter(nestedObjList, 'j', {keys: ['name.0.first']})
// [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}]
// matchSorter(nestedObjList, 'j', {keys: ['name[0].first']}) does not work
This even works with arrays of multiple nested objects: just specify the key
using dot-notation with the *
wildcard instead of a numeric index.
const nestedObjList = [
{aliases: [{name: {first: 'Janice'}},{name: {first: 'Jen'}}]},
{aliases: [{name: {first: 'Fred'}},{name: {first: 'Frederic'}}]},
{aliases: [{name: {first: 'George'}},{name: {first: 'Georgie'}}]},
]
matchSorter(nestedObjList, 'jen', {keys: ['aliases.*.name.first']})
// [{aliases: [{name: {first: 'Janice'}},{name: {first: 'Jen'}}]}]
matchSorter(nestedObjList, 'jen', {keys: ['aliases.0.name.first']})
// []
Property Callbacks: Alternatively, you may also pass in a callback function that resolves the value of the key(s) you wish to match on. This is especially useful when interfacing with libraries such as Immutable.js
const list = [{name: 'Janice'}, {name: 'Fred'}, {name: 'George'}, {name: 'Jen'}]
matchSorter(list, 'j', {keys: [item => item.name]})
// [{name: 'Janice'}, {name: 'Jen'}]
For more complex structures, expanding on the nestedObjList
example above, you
can use map
:
const nestedObjList = [
{
name: [
{first: 'Janice', last: 'Smith'},
{first: 'Jon', last: 'Doe'},
],
},
{
name: [
{first: 'Fred', last: 'Astaire'},
{first: 'Jenny', last: 'Doe'},
{first: 'Wilma', last: 'Flintstone'},
],
},
]
matchSorter(nestedObjList, 'doe', {
keys: [
item => item.name.map(i => i.first),
item => item.name.map(i => i.last),
],
})
// [name: [{ first: 'Janice', last: 'Smith' },{ first: 'Jon', last: 'Doe' }], name: [{ first: 'Fred', last: 'Astaire' },{ first: 'Jenny', last: 'Doe' },{ first: 'Wilma', last: 'Flintstone' }]]
Threshold: You may specify an individual threshold for specific keys. A key will only match if it meets the specified threshold. For more information regarding thresholds see below
const list = [
{name: 'Fred', color: 'Orange'},
{name: 'Jen', color: 'Red'},
]
matchSorter(list, 'ed', {
keys: [{threshold: matchSorter.rankings.STARTS_WITH, key: 'name'}, 'color'],
})
//[{name: 'Jen', color: 'Red'}]
Min and Max Ranking: You may restrict specific keys to a minimum or maximum ranking by passing in an object. A key with a minimum rank will only get promoted if there is at least a simple match.
const tea = [
{tea: 'Earl Grey', alias: 'A'},
{tea: 'Assam', alias: 'B'},
{tea: 'Black', alias: 'C'},
]
matchSorter(tea, 'A', {
keys: ['tea', {maxRanking: matchSorter.rankings.STARTS_WITH, key: 'alias'}],
})
// without maxRanking, Earl Grey would come first because the alias "A" would be CASE_SENSITIVE_EQUAL
// `tea` key comes before `alias` key, so Assam comes first even though both match as STARTS_WITH
// [{tea: 'Assam', alias: 'B'}, {tea: 'Earl Grey', alias: 'A'},{tea: 'Black', alias: 'C'}]
const tea = [
{tea: 'Milk', alias: 'moo'},
{tea: 'Oolong', alias: 'B'},
{tea: 'Green', alias: 'C'},
]
matchSorter(tea, 'oo', {
keys: ['tea', {minRanking: matchSorter.rankings.EQUAL, key: 'alias'}],
})
// minRanking bumps Milk up to EQUAL from CONTAINS (alias)
// Oolong matches as STARTS_WITH
// Green is missing due to no match
// [{tea: 'Milk', alias: 'moo'}, {tea: 'Oolong', alias: 'B'}]
number
Default: MATCHES
Thresholds can be used to specify the criteria used to rank the results. Available thresholds (from top to bottom) are:
const fruit = ['orange', 'apple', 'grape', 'banana']
matchSorter(fruit, 'ap', {threshold: matchSorter.rankings.NO_MATCH})
// ['apple', 'grape', 'orange', 'banana'] (returns all items, just sorted by best match)
const things = ['google', 'airbnb', 'apple', 'apply', 'app'],
matchSorter(things, 'app', {threshold: matchSorter.rankings.EQUAL})
// ['app'] (only items that are equal)
const otherThings = ['fiji apple', 'google', 'app', 'crabapple', 'apple', 'apply']
matchSorter(otherThings, 'app', {threshold: matchSorter.rankings.WORD_STARTS_WITH})
// ['app', 'apple', 'apply', 'fiji apple'] (everything that matches with "word starts with" or better)
boolean
Default: false
By default, match-sorter will strip diacritics before doing any comparisons. This is the default because it makes the most sense from a UX perspective.
You can disable this behavior by specifying keepDiacritics: true
const thingsWithDiacritics = [
'jalapeño',
'à la carte',
'café',
'papier-mâché',
'à la mode',
]
matchSorter(thingsWithDiacritics, 'aa')
// ['jalapeño', 'à la carte', 'à la mode', 'papier-mâché']
matchSorter(thingsWithDiacritics, 'aa', {keepDiacritics: true})
// ['jalapeño', 'à la carte']
matchSorter(thingsWithDiacritics, 'à', {keepDiacritics: true})
// ['à la carte', 'à la mode']
function(itemA, itemB): -1 | 0 | 1
Default: (a, b) => String(a.rankedValue).localeCompare(b.rankedValue)
By default, match-sorter uses the String.localeCompare
function to tie-break
items that have the same ranking. This results in a stable, alphabetic sort.
const list = ['C apple', 'B apple', 'A apple']
matchSorter(list, 'apple')
// ['A apple', 'B apple', 'C apple']
You can customize this behavior by specifying a custom baseSort
function:
const list = ['C apple', 'B apple', 'A apple']
// This baseSort function will use the original index of items as the tie breaker
matchSorter(list, 'apple', {baseSort: (a, b) => (a.index < b.index ? -1 : 1)})
// ['C apple', 'B apple', 'A apple']
function(rankedItems): rankedItems
Default:
matchedItems => matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort))
By default, match-sorter uses an internal sortRankedValues
function to sort
items after matching them.
You can customize the core sorting behavior by specifying a custom sorter
function:
Disable sorting entirely:
const list = ['appl', 'C apple', 'B apple', 'A apple', 'app', 'applebutter']
matchSorter(list, 'apple', {sorter: rankedItems => rankedItems})
// ['C apple', 'B apple', 'A apple', 'applebutter']
Return the unsorted rankedItems, but in reverse order:
const list = ['appl', 'C apple', 'B apple', 'A apple', 'app', 'applebutter']
matchSorter(list, 'apple', {sorter: rankedItems => [...rankedItems].reverse()})
// ['applebutter', 'A apple', 'B apple', 'C apple']
By default, match-sorter
assumes spaces to be the word separator. However, if
your data has a different word separator, you can use a property callback to
replace your separator with spaces. For example, for snake_case
:
const list = [
{name: 'Janice_Kurtis'},
{name: 'Fred_Mertz'},
{name: 'George_Foreman'},
{name: 'Jen_Smith'},
]
matchSorter(list, 'js', {keys: [item => item.name.replace(/_/g, ' ')]})
// [{name: 'Jen_Smith'}, {name: 'Janice_Kurtis'}]
By default, match-sorter
will return matches from objects where one of the
properties matches the entire search term. For multi-column data sets it can
be beneficial to split words in search string and match each word separately.
This can be done by chaining match-sorter
calls.
The benefit of this is that a filter string of "two words" will match both "two" and "words", but will return rows where the two words are found in different columns as well as when both words match in the same column. For single-column matches it will also return matches out of order (column = "wordstwo" will match just as well as column="twowords", the latter getting a higher score).
function fuzzySearchMultipleWords(
rows, // array of data [{a: "a", b: "b"}, {a: "c", b: "d"}]
keys, // keys to search ["a", "b"]
filterValue: string, // potentially multi-word search string "two words"
) {
if (!filterValue || !filterValue.length) {
return rows
}
const terms = filterValue.split(' ')
if (!terms) {
return rows
}
// reduceRight will mean sorting is done by score for the _first_ entered word.
return terms.reduceRight(
(results, term) => matchSorter(results, term, {keys}),
rows,
)
}
Actually, most of this code was extracted from the very first library I ever wrote: genie!
You might try Fuse.js. It uses advanced math fanciness to get the closest match. Unfortunately what's "closest" doesn't always really make sense. So I extracted this from genie.
Looking to contribute? Look for the Good First Issue label.
Please file an issue for bugs, missing documentation, or unexpected behavior.
Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.
Thanks goes to these people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
MIT
FAQs
Simple, expected, and deterministic best-match sorting of an array in JavaScript
The npm package match-sorter receives a total of 2,077,576 weekly downloads. As such, match-sorter popularity was classified as popular.
We found that match-sorter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.