Skip to content

JSON Logic Guide for Sorcha Blueprints ​

Table of Contents ​

  1. Introduction
  2. Basic Concepts
  3. Operators Reference
  4. Common Patterns
  5. Testing and Validation
  6. Best Practices
  7. Advanced Examples

Introduction ​

JSON Logic is a format for expressing conditional logic and calculations as JSON data structures. In Sorcha blueprints, JSON Logic is used for:

  • Conditional routing - Route actions to different participants based on data
  • Calculations - Compute derived values from action data
  • Conditional display - Show/hide form fields based on conditions

Why JSON Logic? ​

✓ Declarative - Logic expressed as data, not code ✓ Portable - Same logic runs in frontend, backend, and blockchain ✓ Sandboxed - Cannot execute arbitrary code (secure) ✓ Serializable - Store logic in database, send over network ✓ Dynamic - Change logic without code deployment ✓ Auditable - Logic is transparent and traceable


Basic Concepts ​

Structure ​

JSON Logic expressions are JSON objects where:

  • Keys are operators
  • Values are operands (can be nested expressions)
json
{
  "operator": [operand1, operand2, ...]
}

Variable References ​

Access data using the var operator:

json
{"var": "fieldName"}

Nested fields use dot notation:

json
{"var": "address.city"}

Default values if variable is missing:

json
{"var": ["fieldName", "defaultValue"]}

Operators Reference ​

Comparison Operators ​

Equality ​

json
{"==": [{"var": "status"}, "approved"]}

Checks if status equals "approved"

Inequality ​

json
{"!=": [{"var": "status"}, "rejected"]}

Checks if status is not "rejected"

Greater Than ​

json
{">": [{"var": "amount"}, 1000]}

Checks if amount is greater than 1000

Greater Than or Equal ​

json
{">=": [{"var": "age"}, 18]}

Checks if age is 18 or more

Less Than ​

json
{"<": [{"var": "score"}, 50]}

Checks if score is less than 50

Less Than or Equal ​

json
{"<=": [{"var": "count"}, 100]}

Checks if count is 100 or less

Logical Operators ​

AND ​

json
{
  "and": [
    {">": [{"var": "amount"}, 5000]},
    {"==": [{"var": "department"}, "finance"]}
  ]
}

Both conditions must be true

OR ​

json
{
  "or": [
    {"==": [{"var": "priority"}, "urgent"]},
    {">": [{"var": "amount"}, 10000]}
  ]
}

At least one condition must be true

NOT ​

json
{"!": {"==": [{"var": "status"}, "draft"]}}

Inverts the result (true → false, false → true)

Truthy Check ​

json
{"!!": {"var": "optionalField"}}

Converts value to boolean (checks if field exists and is truthy)

Arithmetic Operators ​

Addition ​

json
{"+": [{"var": "subtotal"}, {"var": "tax"}]}

Multiple values:

json
{"+": [10, 20, 30]} // Result: 60

Subtraction ​

json
{"-": [{"var": "total"}, {"var": "discount"}]}

Multiplication ​

json
{"*": [{"var": "quantity"}, {"var": "unitPrice"}]}

Division ​

json
{"/": [{"var": "total"}, {"var": "count"}]}

Modulo ​

json
{"%": [{"var": "value"}, 10]}

Returns remainder of division

Conditional (If-Then-Else) ​

The if operator supports multiple conditions:

json
{
  "if": [
    condition1, resultIfTrue1,
    condition2, resultIfTrue2,
    defaultResult
  ]
}

Example:

json
{
  "if": [
    {">": [{"var": "amount"}, 10000]},
    "director",

    {">": [{"var": "amount"}, 5000]},
    "manager",

    "auto-approve"
  ]
}

Evaluates to:

  • "director" if amount > 10,000
  • "manager" if amount > 5,000
  • "auto-approve" otherwise

Array Operators ​

Map ​

Transform each item in an array:

json
{
  "map": [
    {"var": "items"},
    {"*": [{"var": ""}, 2]}
  ]
}

Doubles each value in the array. Empty string "" refers to current item.

Filter ​

Select items matching a condition:

json
{
  "filter": [
    {"var": "items"},
    {">": [{"var": ""}, 10]}
  ]
}

Returns only items greater than 10

Reduce ​

Aggregate array values:

json
{
  "reduce": [
    {"var": "items"},
    {"+": [{"var": "accumulator"}, {"var": "current"}]},
    0
  ]
}

Sums all items (initial value is 0)

All ​

Check if all items match a condition:

json
{
  "all": [
    {"var": "items"},
    {">": [{"var": ""}, 0]}
  ]
}

Returns true if all items are positive

Some ​

Check if any item matches a condition:

json
{
  "some": [
    {"var": "items"},
    {"==": [{"var": ""}, 5]}
  ]
}

Returns true if array contains value 5

In (Contains) ​

Check if value is in array:

json
{"in": [5, {"var": "items"}]}

Or check if substring in string:

json
{"in": ["foo", {"var": "text"}]}

String Operators ​

Concatenation ​

json
{"cat": ["Hello, ", {"var": "name"}, "!"]}

Result: "Hello, John!"

Substring ​

json
{"substr": [{"var": "text"}, 0, 5]}

Extract first 5 characters


Common Patterns ​

Conditional Routing in Blueprints ​

Route to different participants based on amount:

csharp
.RouteConditionally(c => c
    .When(jl => jl.GreaterThan("amount", 10000))
    .ThenRoute("director")

    .When(jl => jl.GreaterThan("amount", 5000))
    .ThenRoute("manager")

    .ElseRoute("auto-approve"))

Generated JSON Logic:

json
{
  "if": [
    {">": [{"var": "amount"}, 10000]},
    "director",
    {">": [{"var": "amount"}, 5000]},
    "manager",
    "auto-approve"
  ]
}

Complex Conditions ​

Combine multiple criteria:

csharp
.RouteConditionally(c => c
    .When(jl => jl.And(
        jl.GreaterThan("amount", 5000),
        jl.Equals("department", "finance")))
    .ThenRoute("cfo")

    .When(jl => jl.Or(
        jl.Equals("priority", "urgent"),
        jl.GreaterThan("amount", 10000)))
    .ThenRoute("director")

    .ElseRoute("manager"))

Generated JSON Logic:

json
{
  "if": [
    {
      "and": [
        {">": [{"var": "amount"}, 5000]},
        {"==": [{"var": "department"}, "finance"]}
      ]
    },
    "cfo",
    {
      "or": [
        {"==": [{"var": "priority"}, "urgent"]},
        {">": [{"var": "amount"}, 10000]}
      ]
    },
    "director",
    "manager"
  ]
}

Calculations ​

Calculate total price:

csharp
.Calculate("totalPrice", c => c
    .WithExpression(c.Multiply(
        c.Variable("quantity"),
        c.Variable("unitPrice"))))

Generated JSON Logic:

json
{
  "totalPrice": {
    "*": [{"var": "quantity"}, {"var": "unitPrice"}]
  }
}

Calculate discount (10%):

csharp
.Calculate("discount", c => c
    .WithExpression(c.Multiply(
        c.Variable("totalPrice"),
        c.Constant(0.1))))

Generated JSON Logic:

json
{
  "discount": {
    "*": [{"var": "totalPrice"}, 0.1]
  }
}

Conditional Form Fields ​

Show field only when status is "rejected":

csharp
.AddControl(ctrl => ctrl
    .OfType(ControlTypes.TextArea)
    .WithTitle("Rejection Reason")
    .BoundTo("/rejectionReason")
    .ShowWhen(jl => jl.Equals("status", "rejected")))

Testing and Validation ​

Using JsonLogicTester ​

csharp
using Sorcha.Blueprint.Engine.Testing;

var tester = new JsonLogicTester(jsonLogicEvaluator);

var expression = JsonNode.Parse(@"{
  ""if"": [
    {"">"""": [{""var"": ""amount""}, 10000]},
    ""director"",
    ""manager""
  ]
}");

var testCases = new[]
{
    JsonLogicTester.CreateTest("High amount")
        .WithInput("amount", 15000)
        .ExpectOutput("director")
        .Build(),

    JsonLogicTester.CreateTest("Medium amount")
        .WithInput("amount", 5000)
        .ExpectOutput("manager")
        .Build(),

    JsonLogicTester.CreateTest("Low amount")
        .WithInput("amount", 1000)
        .ExpectOutput("manager")
        .Build()
};

var report = await tester.RunTestsAsync(expression, testCases);

Console.WriteLine(report); // "Tests: 3/3 passed (100%) in 12.34ms"

Using JsonLogicValidator ​

csharp
using Sorcha.Blueprint.Engine.Validation;
using JsonSchema.Net;

var validator = new JsonLogicValidator();

var expression = JsonNode.Parse(@"{
  "">"": [{""var"": ""amount""}, 1000]
}");

var schema = JsonSchema.FromText(@"{
  ""type"": ""object"",
  ""properties"": {
    ""amount"": { ""type"": ""number"" }
  }
}");

var result = validator.Validate(expression, schema);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine($"Error: {error}");
    }
}

Best Practices ​

1. Keep Expressions Simple ​

✗ Bad - Too complex, hard to understand:

json
{
  "and": [
    {
      "or": [
        {"and": [{">": [{"var": "a"}, 5]}, {"<": [{"var": "b"}, 10]}]},
        {"and": [{">": [{"var": "c"}, 3]}, {"<": [{"var": "d"}, 7]}]}
      ]
    },
    {"!=": [{"var": "status"}, "cancelled"]}
  ]
}

✓ Good - Break into multiple calculations:

json
{
  "calculations": {
    "validRange": {
      "and": [
        {">": [{"var": "a"}, 5]},
        {"<": [{"var": "b"}, 10]}
      ]
    },
    "isActive": {
      "!=": [{"var": "status"}, "cancelled"]
    }
  },
  "condition": {
    "and": [
      {"var": "validRange"},
      {"var": "isActive"}
    ]
  }
}

2. Use Descriptive Variable Names ​

✗ Bad:

json
{"var": "a"}

✓ Good:

json
{"var": "requestedAmount"}

3. Provide Default Values ​

✗ Bad - Fails if field missing:

json
{">": [{"var": "amount"}, 1000]}

✓ Good - Uses default:

json
{">": [{"var": ["amount", 0]}, 1000]}

4. Validate Against Schema ​

Always validate expressions against the action's data schema to catch variable reference errors early.

5. Test Edge Cases ​

Test your expressions with:

  • Minimum/maximum values
  • Null/undefined values
  • Empty strings/arrays
  • Boundary conditions

6. Document Complex Logic ​

Add comments in the blueprint metadata:

json
{
  "metadata": {
    "routingLogic": "Routes to director for amounts over $10k, manager for $5-10k, auto-approves below $5k"
  }
}

Advanced Examples ​

Multi-Party Supply Chain Routing ​

json
{
  "calculations": {
    "totalAmount": {
      "reduce": [
        {
          "map": [
            {"var": "items"},
            {"*": [{"var": "quantity"}, {"var": "unitPrice"}]}
          ]
        },
        {"+": [{"var": "accumulator"}, {"var": "current"}]},
        0
      ]
    },
    "requiresFinanceApproval": {
      "or": [
        {">": [{"var": "totalAmount"}, 5000]},
        {"in": [{"var": "paymentTerms"}, ["net-60", "net-90"]]}
      ]
    },
    "requiresQualityCheck": {
      "some": [
        {"var": "items"},
        {"in": [{"var": "category"}, ["electronics", "medical"]]}
      ]
    }
  },
  "condition": {
    "if": [
      {"==": [{"var": "decision"}, "rejected"]},
      null,

      {"var": "requiresQualityCheck"},
      "quality",

      {"var": "requiresFinanceApproval"},
      "finance",

      "buyer"
    ]
  }
}

This expression:

  1. Calculates total order amount from line items
  2. Determines if finance approval needed (amount > $5k OR extended payment terms)
  3. Checks if quality inspection required (electronics or medical items)
  4. Routes accordingly:
    • Rejected orders end workflow (null)
    • Quality check needed → quality dept
    • Finance approval needed → finance dept
    • Otherwise → buyer

Dynamic Discount Calculation ​

json
{
  "discount": {
    "if": [
      {">": [{"var": "quantity"}, 100]},
      {"*": [{"var": "totalPrice"}, 0.15]},

      {">": [{"var": "quantity"}, 50]},
      {"*": [{"var": "totalPrice"}, 0.10]},

      {">": [{"var": "quantity"}, 10]},
      {"*": [{"var": "totalPrice"}, 0.05]},

      0
    ]
  },
  "finalPrice": {
    "-": [{"var": "totalPrice"}, {"var": "discount"}]
  }
}

Tiered discount:

  • 15% for 100+ items
  • 10% for 50-99 items
  • 5% for 10-49 items
  • No discount below 10 items

See Also ​

Released under the MIT License.