JexlNet 1.1.0
See the version list below for details.
dotnet add package JexlNet --version 1.1.0
NuGet\Install-Package JexlNet -Version 1.1.0
<PackageReference Include="JexlNet" Version="1.1.0" />
paket add JexlNet --version 1.1.0
#r "nuget: JexlNet, 1.1.0"
// Install JexlNet as a Cake Addin #addin nuget:?package=JexlNet&version=1.1.0 // Install JexlNet as a Cake Tool #tool nuget:?package=JexlNet&version=1.1.0
JexlNet
A C# based JEXL parser and evaluator.
NOTE: This library handles the JEXL from TomFrost's JEXL library. It does NOT handle the similarly-named Apache Commons JEXL language.
Quick start
Expressions can be evaluated synchronously or asynchronously by using the Eval
and EvalAsync
methods respectively.
Context should be a Dictionary<string, dynamic> and can internally use List<dynamic>, string, bool and decimal (all numbers should be decimals to allow exact comparisons). Bindings for Json.Net (Newtonsoft) and System.Text.Json can be used to easily convert JSON strings to the required format.
The Grammar can be expanded by adding new operators, functions and transforms.
// Native way of defining a context
var context = new Dictionary<string, dynamic> {
{ "name", new Dictionary<string, dynamic> {
{ "first", "Sterling" },
{ "last", "Archer" }
}},
{ "assoc", new List<dynamic> {
new Dictionary<string, dynamic> {
{ "first", "Lana" },
{ "last", "Kane" }
},
new Dictionary<string, dynamic> {
{ "first", "Cyril" },
{ "last", "Figgis" }
},
new Dictionary<string, dynamic> {
{ "first", "Pam" },
{ "last", "Poovey" }
}
}},
{ "age", 36 }
};
// Or by using System.Text.Json or Json.Net (requires separate package JexlNet.JsonNet to be installed)
string contextJson =
@"{
""name"": {
""first"": ""Sterling"",
""last"": ""Archer""
},
""assoc"": [
{
""first"": ""Lana"",
""last"": ""Kane""
},
{
""first"": ""Cyril"",
""last"": ""Figgis""
},
{
""first"": ""Pam"",
""last"": ""Poovey""
}
],
""age"": 36
}";
// with System.Text.Json
JsonElement contextJsonElement = JsonDocument.Parse(contextJson).RootElement;
Dictionary<string, dynamic?> context = ContextHelpers.ConvertJsonElement(contextJsonElement);
// or with Json.Net (Newtonsoft)
JObject contextJObject = JObject.Parse(contextJson);
Dictionary<string, dynamic?>? context = JexlNet.JsonNet.ContextHelpers.ConvertJObject(contextJsonElement);
// Initialize Jexl
var jexl = new Jexl();
// Use it with asynchronously or synchronously:
// Filter an array asynchronously...
await jexl.EvalAsync(@"assoc[.first == ""Lana""].last", context);
// Kane
// Or synchronously!
jexl.Eval(@"assoc[.first == ""Lana""].last", context);
// Kane
// Do math
await jexl.Eval(@"age * (3 - 1)", context);
// 72
// Concatenate
await jexl.EvalAsync(@"name.first + "" "" + name[""la"" + ""st""]", context);
// "Sterling Archer"
// Compound
await jexl.EvalAsync(
'assoc[.last == "Figgis"].first == "Cyril" && assoc[.last == "Poovey"].first == "Pam"',
context
)
// true
// Use array indexes and return objects
await jexl.EvalAsync(@"assoc[1]", context);
// new Dictionary<string, dynamic> {
// { "first", "Cyril" },
// { "last", "Figgis" }
// }
//
// Use conditional logic
await jexl.EvalAsync(@"age > 62 ? ""retired"" : ""working""", context);
// "working"
// Transform
jexl.Grammar.AddTransform("upper", (dynamic? val) => val?.ToString().ToUpper());
await jexl.EvalAsync(@"""duchess""|upper + "" "" + name.last|upper", context);
// "DUCHESS ARCHER"
// Transform asynchronously, with arguments
jexl.Grammar.AddTransform("getStat", async (dynamic?[] args) => await DbSelectByLastName(args[0], args[1]));
try {
await jexl.EvalAsync(@"name.last|getStat(""weight"")", context);
// Output: 184
} catch (e) {
console.log('Database Error', e.stack)
}
// Functions too, sync or async, args or no args
jexl.Grammar.AddFunction("getOldestAgent", GetOldestAgent);
await jexl.EvalAsync(@"age == getOldestAgent().age", context);
// false
// Add your own (a)synchronous operators
// Here's a case-insensitive string equality
jexl.Grammar.AddBinaryOperator("_=", 20, (dynamic?[] args) => args[0]?.ToLower() == args[1]?.ToLower());
await jexl.EvalAsync(@"""Guest"" _= ""gUeSt""");
// true
// Compile your expression once, evaluate many times!
const { expr } = jexl
var danger = jexl.CreateExpression(@"""Danger "" + place");
await danger.EvalAsync(new Dictionary<string, dynamic> { { "place", "Zone" } }); // Danger zone
await danger.EvalAsync(new Dictionary<string, dynamic> { { "place", "ZONE!!!" } }); // Danger ZONE!!! (Doesn't recompile the expression!)
Play with it
- Jexl Playground - An interactive Jexl sandbox by Christian Zosel @czosel.
Installation
Install from NuGet:
Install-Package JexlNet
Add using statement:
using JexlNet;
And use it:
var jexl = new Jexl();
var result = jexl.Eval("1 + 1");
Async vs Sync: Which to use
There is little performance difference between EvalAsync
and Eval
. Both support async functions and transforms. The only difference is that EvalAsync
returns a Task<dynamic>
and Eval
returns a dynamic
.
All the details
Unary Operators
Operation | Symbol |
---|---|
Negate | ! |
Binary Operators
Operation | Symbol |
---|---|
Add, Concat | + |
Subtract | - |
Multiply | * |
Divide | / |
Divide and floor | // |
Modulus | % |
Power of | ^ |
Logical AND | && |
Logical OR | || |
Comparisons
Comparison | Symbol |
---|---|
Equal | == |
Not equal | != |
Greater than | > |
Greater than or equal | >= |
Less than | < |
Less than or equal | ⇐ |
Element in array or string | in |
A note about in
The in
operator can be used to check for a substring:
"Cad" in "Ron Cadillac"
, and it can be used to check for an array element:
"coarse" in ['fine', 'medium', 'coarse']
. However, the ==
operator is used
behind-the-scenes to search arrays, so it should not be used with arrays of
objects. The following expression returns false: {a: 'b'} in [{a: 'b'}]
.
Ternary operator
Conditional expressions check to see if the first segment evaluates to a truthy value. If so, the consequent segment is evaluated. Otherwise, the alternate is. If the consequent section is missing, the test result itself will be used instead.
Expression | Result |
---|---|
"" ? "Full" : "Empty" | Empty |
"foo" in "foobar" ? "Yes" : "No" | Yes |
{agent: "Archer"}.agent ?: "Kane" | Archer |
Native Types
Type | Examples |
---|---|
Booleans | true , false |
Strings | "Hello "user"", 'Hey there!' |
Numerics | 6, -7.2, 5, -3.14159 |
Objects | {hello: "world!"} |
Arrays | ['hello', 'world!'] |
Groups
Parentheses work just how you'd expect them to:
Expression | Result |
---|---|
(83 + 1) / 2 | 42 |
1 < 3 && (4 > 2 || 2 > 4) | true |
Identifiers
Access variables in the context object by just typing their name. Objects can be traversed with dot notation, or by using brackets to traverse to a dynamic property name.
Example context:
var context = new Dictionary<string, dynamic>
{
{ "name", new Dictionary<string, dynamic> {
{ "first", "Malory" },
{ "last", "Archer" }
}},
{ "exes", new List<string> {
"Nikolai Jakov",
"Len Trexler",
"Burt Reynolds"
}},
{ "lastEx", 2 }
};
Expression | Result |
---|---|
name.first | Malory |
name['la' + 'st'] | Archer |
exes[2] | Burt Reynolds |
exes[lastEx - 1] | Len Trexler |
Collections
Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be a list of the objects for which the filter expression resulted in a truthy value.
Example context:
var context = new Dictionary<string, dynamic>
{
{
"employees", new List<dynamic>
{
new Dictionary<string, dynamic> { { "first", "Sterling" }, { "last", "Archer" }, { "age", 36 } },
new Dictionary<string, dynamic> { { "first", "Malory" }, { "last", "Archer" }, { "age", 75 } },
new Dictionary<string, dynamic> { { "first", "Lana" }, { "last", "Kane" }, { "age", 33 } },
new Dictionary<string, dynamic> { { "first", "Cyril" }, { "last", "Figgis" }, { "age", 45 } },
new Dictionary<string, dynamic> { { "first", "Cheryl" }, { "last", "Tunt" }, { "age", 28 } }
}
},
{ "retireAge", 62 }
};
Expression | Result |
---|---|
employees[.first == 'Sterling'] | [{first: 'Sterling', last: 'Archer', age: 36}] |
employees[.last == 'Tu' + 'nt'].first | Cheryl |
employees[.age >= 30 && .age < 40] | [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}] |
employees[.age >= 30 && .age < 40][.age < 35] | [{first: 'Lana', last: 'Kane', age: 33}] |
employees[.age >= retireAge].first | Malory |
Transforms
The power of Jexl is in transforming data, synchronously or asynchronously.
Transform functions take one argument or an array or list of more arguments.
The first argument is the value to be transformed, and the rest are any other
arguments passed to the transform in the expression. They must return either
the transformed value, or a Promise that resolves with the transformed
value. Add them with jexl.AddTransform(name, function)
.
Arguments can be dynamic?
, dynamic?[]
or List<dynamic?>
.
jexl.Grammar.AddTransform("split", (dynamic?[] args) => args[0]?.Split(args[1]));
jexl.Grammar.AddTransform("lower", (dynamic? val) => val?.ToLower());
Expression | Result |
---|---|
"Pam Poovey"|lower|split(' ')[1] | poovey |
"password==guest"|split('=' + '=') | ['password', 'guest'] |
Functions
While Transforms are the preferred way to change one value into another value,
Jexl also allows top-level expression functions to be defined. Use these to
provide access to functions that either don't require an input, or require
multiple equally-important inputs. They can be added with
jexl.AddFunction(name, function)
. Like transforms, functions can return a
value, or a Promise that resolves to the resulting value.
For functions, arguments are not required, but if they are defined,
they must be dynamic?
, dynamic?[]
or List<dynamic?>
.
jexl.Grammar.AddFunction("getTrue", () => true);
jexl.Grammar.AddFunction("min", (List<dynamic?> args) => args.Min());
jexl.Grammar.AddFunction("expensiveQuery", Db.RunExpensiveQuery);
Expression | Result | ||
---|---|---|---|
false | getTrue() | true | |
min(4, 2, 19) | 2 | ||
counts.missions || expensiveQuery() | Query only runs if needed |
Context
Variable contexts are Dictionary objects that can be accessed in the expression, but they have a hidden feature: they can include an invocable function. This function will be called with the name of the variable being accessed, and the result will be used as the value of that variable. This allows for dynamic variable resolution, such as accessing a database or file system.
```csharp
var func = new Func<Task<object?>>(async () =>
{
await Task.Delay(100);
return "bar";
});
var context = new Dictionary<string, dynamic> {
{ "foo", func }
};
await jexl.EvalAsync(@"foo", context);
// returns "bar" after 100ms
Other implementations
Jexl - The original JavaScript implementation of JEXL. jexl-rs - A Rust-based JEXL parser and evaluator. PyJEXL - A Python-based JEXL parser and evaluator.
License
JexlNet is licensed under the MIT license. Please see LICENSE
for full details.
Credits
This library is a port of TomFrost's JEXL library so all credit goes to the author and the contributors of that library.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on JexlNet:
Package | Downloads |
---|---|
JexlNet.ExtendedGrammar
Extended grammar for JexkNet, the powerful context-based expression parser and evaluator in C# using JEXL (Javascript Expression Language) expressions. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.6.0 | 648 | 12/5/2024 |
2.5.4 | 427 | 11/12/2024 |
2.5.3 | 309 | 11/6/2024 |
2.5.2 | 135 | 11/5/2024 |
2.5.1 | 128 | 11/5/2024 |
2.4.6 | 143 | 10/31/2024 |
2.4.5 | 140 | 10/31/2024 |
2.4.4 | 449 | 10/8/2024 |
2.4.3 | 390 | 9/26/2024 |
2.4.1 | 123 | 9/25/2024 |
2.4.0 | 273 | 9/18/2024 |
2.3.6 | 189 | 9/17/2024 |
2.3.5 | 233 | 9/10/2024 |
2.3.3 | 189 | 9/5/2024 |
2.3.1 | 385 | 7/27/2024 |
2.3.0 | 367 | 7/12/2024 |
2.2.11 | 174 | 7/12/2024 |
2.2.10 | 151 | 7/2/2024 |
2.2.9 | 152 | 6/27/2024 |
2.2.5 | 660 | 4/15/2024 |
2.2.4 | 144 | 3/29/2024 |
2.2.3 | 191 | 3/9/2024 |
2.2.2 | 165 | 3/9/2024 |
2.2.1 | 166 | 3/5/2024 |
2.2.0 | 172 | 2/20/2024 |
2.1.6 | 131 | 2/20/2024 |
2.1.5 | 118 | 2/20/2024 |
2.1.4 | 111 | 2/20/2024 |
2.1.3 | 117 | 2/20/2024 |
2.1.2 | 152 | 2/9/2024 |
2.1.1 | 121 | 2/3/2024 |
2.0.0 | 123 | 1/26/2024 |
1.2.0 | 145 | 1/12/2024 |
1.1.2 | 170 | 12/23/2023 |
1.1.1 | 147 | 12/23/2023 |
1.1.0 | 144 | 12/23/2023 |
1.0.1 | 144 | 12/23/2023 |
1.0.0 | 139 | 12/23/2023 |