gval: evaluating Go-like expressions at runtime, from Paessler
Expression evaluation in golang
At a glance
- What is it?
- gval is a small Go library that evaluates expressions written in a Go-like syntax against parameters you supply, and it is the kind of dependency you pull in when a monitoring rule, a filter or a policy check has to be editable at runtime. It is careful about operator precedence and about what it costs, and its release history is old even though the code was pushed to recently.
- Who is it for?
- Adopt gval when you need to let a user write a rule that your Go program evaluates, especially when the same expression is evaluated against many parameter sets, since parsing once and reusing the result is the design the library is built around.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 41 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
gval exists for the rule that has to change without a redeploy
Gval, short for Go eVALuate, provides support for evaluating arbitrary expressions, in particular Go-like expressions. That is the whole scope, and the small size of the codebase reflects it. The repository is a flat set of Go files: gval.go, language.go, operator.go, functions.go, parse.go, parser.go and evaluable.go, with the matching test files for each, plus a benchmarks_test.go and an example_test.go that doubles as the documentation.
The use case is specific. A monitoring product, a rule engine, a filter for a query, a threshold check: somewhere the person using your software wants to write a condition, and you do not want to give them Go. gval lets you accept that condition as a string and evaluate it against values your program already has.
The expression surface is more capable than a comparison operator would suggest. The upstream docs list basic expressions such as 10 > 0, parameterized ones such as foo > 0, nested ones such as foo.bar > 0, arithmetic such as (requests_made * requests_succeeded / 100) >= 90, string comparison such as http_response_body == "service is ok", and float division such as (mem_used / total_mem) * 100. Every one of those is a shape that shows up in real threshold rules, which is the point of shipping a parser instead of asking users to assemble conditions through a form.
The module is small in dependencies too, with go.mod naming just two requires: github.com/PaesslerAG/jsonpath v0.1.0 and github.com/shopspring/decimal v1.3.1.
One full language, assembled from sub languages you can pick apart
The default language is described as being composed of several sub languages, of which text, arithmetic and propositional logic are given as examples. All of them are merged into gval.Full, and the documentation enumerates its elements. Modifiers cover arithmetic and bitwise work, including addition, subtraction, division, multiplication, the bitwise operators, exponentiation, modulo, and the two shift operators. Comparators cover > >= < <= == != and the regular expression operators =~ and !~. Logical operators are || and &&.
Literals are typed in a specific way, and this is where the first surprise sits. Numeric constants are 64-bit floating point, with the upstream notes giving 12345.678 as the example, so a bare integer in an expression is a float rather than an int. String constants use double quotes, "foobar", and the boolean constants are true and false. Date values come from a Date(x) function that accepts any permutation of RFC3339, ISO8601, ruby date, or unix date.
There are also JSON-style compound literals, with Json Arrays written as [1, 2, "foo"] and Json Objects as {"a":1, "b":2, "c":"foo"}. Prefixes are ! - ~, the ternary conditional is ? :, and null coalescence is ??.
Because the sub languages are separable, you can assemble your own language instead of taking the full set. Every constant, function or operator can be defined separately, and existing expression languages can be reused. The performance section makes a direct recommendation along those lines: if performance matters, create your expression language with what you actually need rather than accepting gval.Full wholesale.
Parameters come from maps or structs, and hyphens will be read as subtraction
Variables are accessed through string literals. The rule is about the shape of the parameter rather than the expression: a value with string keys works if the parameter is a map[string]interface{} or a map[interface{}]interface{}, and fields and methods work if the parameter is a struct.
Three access styles cover most cases. The bracket selector reaches map and array elements and struct fields, with foo[0] for an array index and foo["b" + "a" + "r"] showing that the key can itself be computed. The dot selector reaches a nested variable whose name contains only letters and _ characters, as in foo.bar > 0. Custom selectors are the third path, and they exist for a specific failure case.
That failure case is worth internalising. Parameter names like response-time are interpreted as response minus time, because the parser sees the hyphen as the subtraction operator. The upstream docs are clear that gval does not support these parameter names directly, and points to a custom extension such as JSON Path as the way to reach them, with $["response-time"] as the expression form. The same mechanism is said to be suitable for accessing array elements.
If your parameter is a struct, fields and methods are reached in the usual way, including foo.Hello + foo.World(), and it also works when the parameter is the struct directly, as Hello + World(), or when the fields are nested. The library also lets you supply your own selection logic by implementing SelectGVal(ctx context.Context, k string) (interface{}, error) on your struct, where the function receives the next part of the path and can return any type of variable, which is then evaluated through the standard procedures.
Parse once, evaluate many: the reason evaluable.go exists
The single most useful piece of advice in the upstream docs is also the most structural. You can parse gval.Expressions once and re-use them multiple times, because parsing is described as the compute-intensive phase of the process. The guidance is direct: if you intend to use the same expression with different parameters, parse it once.
That advice is what makes gval usable in a loop. A rule that is evaluated against a hundred thousand measurements should not be re-parsed a hundred thousand times, and the Evaluable type in the repository exists to make the parsed form first class rather than an internal detail. The parsing and evaluation example in the godoc is the reference for that pattern.
The trade-off is that the parsed form binds the expression, not the parameters. A different parameter set is fine, but a different expression requires a new parse. If your design produces a new expression string on every iteration, you have gained nothing from the split.
The language also respects the normal Go standard order of operators, which the documentation flags as something the writer of an expression must get right or disambiguate with parentheses. The example given shows how strings, numbers and booleans mix, with a ternary producing a string that is then concatenated, so a rule can be written the way a Go programmer would read it.
Extending the language is a supported path, not a hack
The library is described as completely customizable, and the extension surface is broad enough that you can add a function, an operator or a whole language. The upstream docs give two examples of custom additions: a custom date comparator written as date(`2014-01-02`) > date(`2014-01-01 23:59:59`), and a string length function used as strlen("someReallyLongInputString") <= 16.
A small ecosystem of external languages already exists for gval, and the repository invites additions. gvalstrings parses single quoted strings, which gval does not handle on its own, and jsonpath adds full support for jsonpath inside gval expressions. The jsonpath package is also the one listed in go.mod, at version v0.1.0, alongside shopspring/decimal for decimal arithmetic, which suggests the decimal type is reachable from within an expression rather than only at the Go API boundary.
The performance section is honest in a way that is worth reading closely. The library is built with the intention of being quick, but it has not been aggressively profiled and optimized, and the upstream docs say that for most applications it is completely fine. Then it gives the specific warning: using accessors on structs makes the expression about four times slower than just using a parameter, and points at the benchmarks for more precise measurements on a given system. The recommended alternative is to define functions instead, which is also described as cleaner, since those approaches use no reflection.
The tagged releases stopped in 2024 even though the repository is still being written to
The last push to the repository was on 2026-09-13, and the repository is not archived. The release list tells a different and more important story. The most recent tag is v1.2.4, published on 2024-11-28, preceded by v1.2.3 on 2024-10-18 and v1.2.2 on 2023-02-06. So there is recent work in the tree and no new tag since late 2024, which is the gap to factor into an adoption decision rather than a reason to walk away on its own.
The licence is BSD-3-Clause, a permissive licence that permits reuse inside commercial and closed products without requiring source disclosure, though the licence file and its obligations still govern how the code is distributed.
Two technical details in the module file are worth noting for a modern Go project. go.mod declares go 1.15, which is a low floor and means the package should build under a current toolchain without complaint. The dependency on jsonpath sits at v0.1.0, a pre-1.0 tag, and decimal at v1.3.1, so the usual pre-1.0 API stability caveat applies to one of the two.
The upstream docs also do not describe how to restrict what an expression can do. There is no mention of an execution timeout, a step limit or a sandbox, and the built-in functions include file-free but not obviously dangerous operations. If expressions come from untrusted users, that absence is the gap to close yourself.
Editorial conclusion
Adopt gval when you need to let a user write a rule that your Go program evaluates, especially when the same expression is evaluated against many parameter sets, since parsing once and reusing the result is the design the library is built around. Do not adopt it for configuration formats where a typed decoder already exists, and think twice if your expressions touch struct fields in a hot loop, because the upstream docs put that path at roughly four times the cost of a plain parameter. Before you commit, check that the last tagged release v1.2.4 from 2024-11-28 is new enough for your Go version, and that your parameter names avoid the hyphen case that the parser reads as subtraction.
Frequently asked questions
How do I evaluate a parameterised expression with gval?
Supply the parameter as a map[string]interface{} or map[interface{}]interface{} for string keys, or as a struct for fields and methods, then reference it by name in the expression, for example foo > 0. Nested access uses a dot for names containing only letters and _ characters.
Why is my expression with a hyphen in a parameter name not working?
A name like response-time is read as response minus time, because the hyphen is the subtraction operator. The upstream docs say gval does not support these names directly and points to a custom extension such as JSON Path, where the expression form is $["response-time"].
Should I parse the expression once or every time I evaluate it?
Parse once. The upstream docs identify parsing as the compute-intensive phase and recommends reusing a parsed gval.Expression when the same expression runs against different parameters.
What is the latest version of gval?
The latest tag is v1.2.4, published on 2024-11-28. The repository itself is not archived and the last push was on 2026-09-13.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/paesslerag-gval)Was this useful?