Human friendly search conditions notation parser. Somewhat similar to Gmail "Search email" input.
The parsed output is designed to match condition-builder dump format, so the two play nicely together.
deno
deno add jsr:@marianmeres/condition-parsernodejs
npm i @marianmeres/condition-parserimport { ConditionParser } from "@marianmeres/condition-parser";The core parsable expression:
// for the default "equals" (short "eq") operator
"key:value"
// or with custom operator
"key:operator:value"is parsed internally as
{ key: "key", operator: "operator", value: "value" }You can join multiple ones with and or or. The default and can be omitted, so:
"foo:bar baz:bat or hey:ho 'let\'s':go"is equivalent to
"foo:bar and baz:bat or hey:ho and 'let\'s':go"You can use parentheses to logically group the expressions. You can use escaped quotes (or colons) inside the identifiers:
`"my key":'my \: operator':"my \" value with quotes" and (foo:<:bar or baz:>:bat)`Also, you can mix in arbitrary unparsable content — around a contiguous
parseable middle — which will be preserved. Both leading and trailing
free-text fragments are collected into unparsed (single-space joined):
const result = ConditionParser.parse(
"this is a:b and (c:d or e:f) free text",
options: Partial<ConditionParserOptions> // read below
);
// result is now:
{
parsed: [
{
expression: { key: "a", operator: "eq", value: "b" },
operator: "and"
},
{
condition: [
{ expression: [{ key: "c", operator: "eq", value: "d" }], operator: "or" },
{ expression: [{ key: "e", operator: "eq", value: "f" }], operator: "or" }
],
operator: "and"
}
],
unparsed: "this is free text",
meta: { /* unique keys/operators/values/expressions */ },
errors: [] // non-empty on syntactic problems; see API.md
}
// ConditionParser.parse options (all optional):
// - defaultOperator: string (default "eq") - operator when not specified
// - debug: boolean (default false) - enable debug logging
// - transform: (ctx) => ctx - transform each parsed expression
// - preAddHook: (ctx) => ctx|null|undefined - return falsy to DROP the expressionFor strict validation (e.g. form input), check both:
const ok = errors.length === 0 && unparsed.trim() === "";See API.md for complete API documentation, including the Breaking Changes section for 1.8.0.
See condition-builder for more.
import { ConditionParser } from "@marianmeres/condition-parser";
import { Condition } from "@marianmeres/condition-builder";
const userSearchInput = '(folder:"my projects" or folder:inbox) foo bar';
const options = {
renderKey: (ctx) => `"${ctx.key.replaceAll('"', '""')}"`,
renderValue: (ctx) => `'${ctx.value.toString().replaceAll("'", "''")}'`,
};
const { parsed, unparsed } = ConditionParser.parse(userSearchInput);
const c = new Condition(options);
c.and("user_id", "eq", 123).and(
Condition.restore(parsed, options).and("text", "match", unparsed),
);
assertEquals(
`"user_id"='123' and (("folder"='my projects' or "folder"='inbox') and "text"~*'foo bar')`,
c.toString(),
);