The evaluator package implements a small expression language for querying Go structs. Expressions are represented as Go structs that can be combined using logical operators. Comparison expressions support both numeric and string values.
The Evaluator library allows you to:
- Dynamic Filtering: Define filtering logic at runtime (e.g., from configuration files or user input) rather than hardcoding it.
- Safe Querying: Expose a simple, safe query capability to end-users without exposing full SQL or code execution.
- Portability: Serialize queries to JSON to store them in a database or send them over a network.
- Type Safety: Works with standard Go structs and types.
- Custom Logic: Extend the evaluator with custom functions using
FunctionExpression.
To use the library in your Go project:
go get github.com/arran4/go-evaluatorTo install the command-line tools, you can use one of the following methods:
Download binaries from: https://github.com/arran4/go-evaluator/releases
brew tap arran4/homebrew-tap
brew install csvfilter jsonlfilter jsontest yamltestgo install github.com/arran4/go-evaluator/cmd/csvfilter@latest
go install github.com/arran4/go-evaluator/cmd/jsonlfilter@latest
go install github.com/arran4/go-evaluator/cmd/jsontest@latest
go install github.com/arran4/go-evaluator/cmd/yamltest@latest- Debian/Ubuntu (
.deb): see Releases assets - RPM (
.rpm): see Releases assets
- Equality and inequality checks (
Is,IsNot) - Numeric and lexical comparisons (
GT,GTE,LT,LTE) - Membership checks with
Contains - Logical composition using
And,OrandNot - Custom Functions: Execute arbitrary logic via
FunctionExpression - JSON serialisation for easy storage or transmission of queries
Create a query using the provided expression types and call Evaluate with your
target struct:
q := evaluator.Query{
Expression: &evaluator.AndExpression{Expressions: []evaluator.Query{
{Expression: &evaluator.IsExpression{Field: "Name", Value: "bob"}},
{Expression: &evaluator.GreaterThanExpression{Field: "Age", Value: 30}},
}},
}
matched := q.Evaluate(&User{Name: "bob", Age: 35})This example demonstrates how to integrate the evaluator into an application to filter a list of structs based on a dynamic query string (e.g., from user input).
package main
import (
"fmt"
"log"
"github.com/arran4/go-evaluator/parser/simple"
)
type Product struct {
Name string
Category string
Price float64
InStock bool
}
func main() {
// 1. Data source
products := []Product{
{"Laptop", "Electronics", 999.99, true},
{"Coffee Mug", "Kitchen", 12.50, true},
{"Headphones", "Electronics", 49.99, false},
}
// 2. Query (could come from user input, API, config, etc.)
// Find all Electronics under $1000
queryString := `Category is "Electronics" and Price < 1000`
// 3. Parse the query
query, err := simple.Parse(queryString)
if err != nil {
log.Fatal(err)
}
// 4. Filter the list
var filtered []Product
for _, p := range products {
if query.Evaluate(&p) {
filtered = append(filtered, p)
}
}
// 5. Use results
for _, p := range filtered {
fmt.Printf("Found: %s ($%.2f)\n", p.Name, p.Price)
}
}You can execute arbitrary logic (like math, formatting, or lookups) by implementing the Function interface and using FunctionExpression.
// 1. Implement Function interface
type SumFunc struct{}
func (s *SumFunc) Call(args ...interface{}) (interface{}, error) {
sum := 0.0
for _, arg := range args {
// ... type assertion and summing ...
}
return sum, nil
}
// 2. Use in Expression
expr := evaluator.FunctionExpression{
Func: &SumFunc{},
Args: []evaluator.Term{
evaluator.Constant{Value: 10},
evaluator.Constant{Value: 20},
},
}
result, _ := expr.Evaluate(nil) // 30Queries can be marshalled to and from JSON. This is handy for configuration files or network APIs.
js := `{
"Expression": {
"Type": "Contains",
"Expression": {
"Field": "Tags",
"Value": "go"
}
}
}`
var q evaluator.Query
if err := json.Unmarshal([]byte(js), &q); err != nil {
log.Fatal(err)
}Each query expression implements the Expression interface. The table below
lists the available types and their purpose:
| Type | Purpose |
|---|---|
Is / IsNot |
Check equality or inequality of a field |
GT / GTE |
Numeric or lexical "greater than" comparisons |
LT / LTE |
Numeric or lexical "less than" comparisons |
Contains |
Test that a slice field contains a value |
And / Or / Not |
Compose other expressions logically |
FunctionExpression |
Execute a custom Function implementation |
Example usage:
evaluator.Query{Expression: &evaluator.NotExpression{Expression: evaluator.Query{
Expression: &evaluator.IsExpression{Field: "Deleted", Value: true},
}}}The command-line tools use a simple string syntax to define expressions.
Operators:
is,is not: Equality checks>,>=,<,<=: Numeric/Lexical comparisoncontains: Checks if a list contains a valueand,or,not: Logical operators(...): Grouping
Values:
- Strings:
"value" - Numbers:
123,45.67 - Booleans:
true,false
Examples:
Status is "active"Age >= 18Tags contains "admin"(Role is "admin" or Role is "moderator") and Active is true
The project includes small utilities for working with common data formats.
Filters CSV rows based on headers.
Usage:
# Given data.csv:
# name,age,city
# alice,30,ny
# bob,25,sf
csvfilter -e 'age > 28' data.csv
# Output:
# name,age,city
# alice,30,nyFilters newline-delimited JSON records.
Usage:
# Given logs.jsonl:
# {"level":"info", "msg":"started"}
# {"level":"error", "msg":"failed"}
jsonlfilter -e 'level is "error"' logs.jsonl
# Output:
# {"level":"error", "msg":"failed"}Evaluates a single JSON document (or multiple files). Returns exit code 0 on match, 1 otherwise.
Usage:
# Check if config.json is valid for production
jsontest -e 'environment is "production" and debug is false' config.json
if [ $? -eq 0 ]; then
echo "Production ready"
fiLike jsontest but for YAML documents.
Usage:
yamltest -e 'replicas >= 3' deployment.yamlRun go test ./... to execute the unit tests.
This project is licensed under the MIT License.