Skip to content

JsonLogic Rule Evaluation

recipe JSON Schema C# jsonlogic rule evaluation if

This recipe demonstrates how to evaluate JsonLogic rules against JSON data using the Corvus.Text.Json.JsonLogic library. It covers the string-based convenience API, typed evaluation with workspaces, data access operators, array operations, custom operators, and build-time rule compilation via the source generator.

The Pattern

JsonLogic is a standard format for expressing business rules as JSON objects. Each rule is a JSON object whose single key is the operator name and whose value is the array of arguments. Literal values (strings, numbers, booleans, arrays) pass through unchanged.

In Corvus.Text.Json.JsonLogic, rules are compiled to delegate trees on first use and cached for subsequent evaluations. The evaluator is thread-safe and designed for high-throughput scenarios.

Evaluating Rules

EvaluateToString — the simplest API

For quick evaluations where both input and output are JSON strings:

string? result = JsonLogicEvaluator.Default.EvaluateToString(
    """{"if": [{">=": [{"var": "order.total"}, 100]}, "VIP", "Standard"]}""",
    """{"order": {"customer": "Alice", "total": 150}}""");
// result: "\"VIP\""

Evaluate with parsed documents

For zero-allocation evaluation, parse the rule and data into JsonElement values and provide a JsonWorkspace:

using var ruleDoc = ParsedJsonDocument<JsonElement>.Parse(
    """{"*": [{"var": "order.total"}, 0.9]}""");

using var dataDoc = ParsedJsonDocument<JsonElement>.Parse(orderJson);
using JsonWorkspace workspace = JsonWorkspace.Create();

JsonLogicRule rule = new(ruleDoc.RootElement);
JsonElement result = JsonLogicEvaluator.Default.Evaluate(in rule, dataDoc.RootElement, workspace);

The JsonWorkspace overload avoids cloning the result, so the returned JsonElement remains valid only while the workspace is alive.

Data Access Operators

Operator Description
{"var": "path.to.field"} Read a value from the data using dot-notation
{"var": ["path", default]} Read with a default value if the field is missing
{"missing": ["a", "b", "c"]} Returns an array of which listed fields are missing
// Nested access
JsonLogicEvaluator.Default.EvaluateToString("""{"var": "order.customer"}""", data);
// "Alice"

// Default value
JsonLogicEvaluator.Default.EvaluateToString("""{"var": ["order.discount", 0]}""", data);
// 0

// Find missing fields
JsonLogicEvaluator.Default.EvaluateToString(
    """{"missing": ["order.customer", "order.discount"]}""", data);
// ["order.discount"]

Array Operations

JsonLogic supports filter, map, reduce, all, some, and none for processing arrays:

// Filter: in-stock items over $50
JsonLogicEvaluator.Default.EvaluateToString(
    """{"filter": [{"var": "products"}, {"and": [{"var": "inStock"}, {">": [{"var": "price"}, 50]}]}]}""",
    productsJson);

// Map: extract names
JsonLogicEvaluator.Default.EvaluateToString(
    """{"map": [{"var": "products"}, {"var": "name"}]}""",
    productsJson);

// Reduce: sum prices
JsonLogicEvaluator.Default.EvaluateToString(
    """{"reduce": [{"var": "products"}, {"+": [{"var": "accumulator"}, {"var": "current.price"}]}, 0]}""",
    productsJson);

Custom Operators

Register custom operators by implementing IOperatorCompiler and passing them to a new JsonLogicEvaluator:

file class PercentOperator : IOperatorCompiler
{
    public RuleEvaluator Compile(RuleEvaluator[] operands)
    {
        RuleEvaluator valueEval = operands[0];
        RuleEvaluator percentEval = operands[1];

        return (in JsonElement data, JsonWorkspace ws) =>
        {
            EvalResult value = valueEval(in data, ws);
            EvalResult percent = percentEval(in data, ws);

            if (value.TryGetDouble(out double v) && percent.TryGetDouble(out double p))
            {
                return EvalResult.FromDouble(v * p / 100.0);
            }

            return EvalResult.FromDouble(0);
        };
    }
}

var evaluator = new JsonLogicEvaluator(
    new Dictionary<string, IOperatorCompiler>
    {
        ["percent"] = new PercentOperator(),
    });

evaluator.EvaluateToString("""{"percent": [250, 20]}""", "{}");
// 50

Custom operators are checked before built-in operators, so they can override standard behaviour.

Source-Generated Rules

For maximum performance, compile rules at build time using the [JsonLogicRule] attribute. Add the rule as an AdditionalFiles item and reference the source generator:

File: discount-rule.json

{
    "if": [
        { ">=": [{ "var": "order.total" }, 100] },
        { "*": [{ "var": "order.total" }, 0.9] },
        { "var": "order.total" }
    ]
}
[JsonLogicRule("discount-rule.json")]
public static partial class DiscountRule;

The generator emits a static Evaluate method with constant-folded arithmetic:

using JsonWorkspace workspace = JsonWorkspace.Create();
JsonElement result = DiscountRule.Evaluate(dataDoc.RootElement, workspace);

Running the Example

cd docs/ExampleRecipes/024-JsonLogic
dotnet run
  • 025-JSONata — JSONata expression evaluation (a more expressive query/transformation language)
  • 023-JsonPatch — RFC 6902 JSON Patch operations