Skip to content
CØDE N!NJΔ edited this page Oct 12, 2025 · 2 revisions

JSONPredicate - Developer Guide

Welcome to the JSONPredicate documentation! This library provides a powerful and intuitive way to evaluate string-based predicate expressions against JSON objects using JSONPath syntax in .NET applications.

Table of Contents

  1. Getting Started
  2. Expression Syntax
  3. Operators Reference
  4. Advanced Usage
  5. Best Practices
  6. Performance Considerations
  7. API Reference
  8. Examples
  9. Troubleshooting
  10. Contributing
  11. FAQ

What is JSONPredicate?

JSONPredicate is a lightweight .NET library that allows developers to write expressive queries against JSON objects using a simple, SQL-like syntax combined with JSONPath for property access. It's designed to be fast, type-safe, and easy to use for filtering, validation, and conditional logic operations.

Key Features

  • 🎯 JSONPath Support: Navigate nested object properties using intuitive dot notation
  • 🔧 Rich Operator Set: Support for equality, comparison, containment, and logical operators
  • 🧮 Type Safety: Automatic type conversion and validation for seamless comparisons
  • 🏗️ Complex Expressions: Parentheses grouping, operator precedence, and nested operations
  • High Performance: Optimized evaluation engine with minimal overhead
  • 🪶 Lightweight: Minimal dependencies, fast startup and evaluation times
  • 🕰️ DateTime Support: Built-in handling for various DateTime formats and comparisons
  • 📚 Multi-Framework: Supports .NET Framework 4.6.2, .NET Standard 2.0/2.1, and .NET 9.0
  • 🔗 Array Indexing: NEW in v1.2.0: Access array elements using array[0].property notation
  • 🔤 Extended Operators: NEW in v1.2.0:starts_with, ends_with, and contains string operators
  • 🧵 Thread Safety: NEW in v1.2.0: Thread-safe operation in multi-threaded environments

Quick Example

varcustomer=new{profile=new{name="John Doe",age=25,isActive=true},orders=new[]{"premium","urgent"},score=95.5,lastLogin=DateTime.Parse("2024-08-01T10:30:00Z"),tags=new[]{"vip","early-adopter","premium"}// NEW in v1.2.0: Array for indexing};// Simple equality checkboolisJohn=JSONPredicate.Evaluate("profile.name eq `John Doe`",customer);// Complex logical expressionboolisEligible=JSONPredicate.Evaluate("profile.age gte 18 and profile.isActive eq true and score gt 90",customer);// Array containment with date comparisonboolhasRecentActivity=JSONPredicate.Evaluate("orders in (`premium`, `vip`) and lastLogin gt `2024-07-01`",customer);// NEW in v1.2.0: Array indexingboolisFirstOrderPremium=JSONPredicate.Evaluate("orders[0] eq `premium`",customer);// NEW in v1.2.0: String operatorsboolisVipUser=JSONPredicate.Evaluate("tags[0] starts_with `v`",customer);boolisEarlyAdopter=JSONPredicate.Evaluate("tags contains `early`",customer);boolhasPremiumTag=JSONPredicate.Evaluate("tags[2] ends_with `ium`",customer);

Getting Started

Installation

Install JSONPredicate via NuGet Package Manager:

Package Manager Console

Install-Package JsonPathPredicate

.NET CLI

dotnet add package JsonPathPredicate

PackageReference

<PackageReferenceInclude="JsonPathPredicate"Version="1.0.0" />

Framework Support

FrameworkVersionSupport Status
.NET Framework4.6.2+✅ Full Support
.NET Standard2.0, 2.1✅ Full Support
.NET Core2.0+✅ Full Support
.NET5.0, 6.0, 7.0, 8.0, 9.0✅ Full Support

Basic Usage

  1. Import the namespace:

    usingJSONPredicate;
  2. Create your data object:

    vardata=new{user=new{name="Alice",age=30,roles=new[]{"admin","user"}},settings=new{theme="dark",notifications=true}};
  3. Evaluate expressions:

    boolresult=JSONPredicate.Evaluate("user.age gte 18",data);// Returns: true

Your First Expression

Let's break down a simple expression:

stringexpression="user.name eq `Alice`";boolresult=JSONPredicate.Evaluate(expression,data);

This expression has three parts:

  • JSONPath (user.name): Navigates to the nested property
  • Operator (eq): Specifies the comparison type
  • Value (Alice): The value to compare against (wrapped in backticks)

Expression Syntax

Basic Structure

Every JSONPredicate expression follows this pattern:

[JSONPath] [Operator] [Value]

For complex expressions:

[Expression] [Logical Operator] [Expression]

JSONPath Navigation

JSONPath allows you to navigate through nested object properties using dot notation:

PatternDescriptionExample
propertyRoot level propertyname
object.propertyNested propertyuser.name
object.nested.propertyDeeply nested propertyuser.profile.address.city

Examples:

vardata=new{customer=new{profile=new{personal=new{firstName="John",lastName="Doe"},address=new{street="123 Main St",city="New York",zipCode="10001"}},preferences=new{theme="dark",language="en-US"}}};// Access deeply nested propertiesboolresult1=JSONPredicate.Evaluate("customer.profile.personal.firstName eq `John`",data);boolresult2=JSONPredicate.Evaluate("customer.profile.address.city eq `New York`",data);boolresult3=JSONPredicate.Evaluate("customer.preferences.theme eq `dark`",data);

Value Syntax

Values in expressions must be properly formatted:

String Values

Wrap string values in backticks, single quotes, or double quotes:

// All equivalent"name eq `John`""name eq 'John'""name eq \"John\""

Numeric Values

Numbers can be written directly:

"age eq 25""score eq 95.5""count eq -10"

Boolean Values

Boolean values are case-insensitive:

"isActive eq true""isEnabled eq false"

DateTime Values

DateTime values should be in ISO 8601 format:

"createdAt eq `2024-08-01T10:30:00Z`""birthDate gt `1990-01-01`"

Parentheses for Grouping

Use parentheses to control evaluation order:

// Without parentheses - AND has higher precedence"status eq `active` or role eq `admin` and age gte 18"// Evaluates as: status eq 'active' or (role eq 'admin' and age gte 18)// With parentheses - explicit grouping"(status eq `active` or role eq `admin`) and age gte 18"// Evaluates as: (status eq 'active' or role eq 'admin') and age gte 18

Comments and Whitespace

Expressions are whitespace-tolerant:

// All equivalent"user.name eq `John`""user.name eq `John`"" user.name eq `John` "

Operators Reference

Comparison Operators

Equality Operator (eq)

Tests for equality with automatic type conversion and case-insensitive string comparison.

// String comparison (case-insensitive)"name eq `john`"// matches "John", "JOHN", "john"// Numeric comparison"age eq 25"// matches integer 25"score eq 95.5"// matches double 95.5// Boolean comparison"isActive eq true"// matches boolean true// DateTime comparison"createdAt eq `2024-01-01T00:00:00Z`"// exact DateTime match

Type Conversion Examples:

vardata=new{count="25"};// String valueboolresult=JSONPredicate.Evaluate("count eq 25",data);// Returns true

Inequality Operator (not)

Tests for non-equality.

"status not `inactive`"// true if status is not "inactive""age not 0"// true if age is not 0"isDeleted not true"// true if isDeleted is not true

Contains/In Operator (in)

Tests if a value is contained within a collection or if collections intersect.

Single value against collection:

vardata=new{tags=new[]{"vip","premium","gold"}};// Check if tags contain specific values"tags in (`vip`, `platinum`)"// true (vip exists in tags)"tags in (`basic`, `standard`)"// false (none exist in tags)

Single value membership:

vardata=new{role="admin"};// Check if role is in allowed list"role in (`admin`, `moderator`, `user`)"// true

Collection intersection:

vardata=new{userTags=new[]{"premium","early-access"},requiredTags=new[]{"premium","vip"}};// Check if collections have any common elements"userTags in requiredTags"// true (both have "premium")

Comparison Operators (gt, gte, lt, lte)

Greater Than (gt)

"age gt 18"// age > 18"score gt 90.5"// score > 90.5"createdAt gt `2024-01-01`"// date after 2024-01-01

Greater Than or Equal (gte)

"age gte 18"// age >= 18"rating gte 4.5"// rating >= 4.5

Less Than (lt)

"age lt 65"// age < 65"price lt 100.00"// price < 100.00

Less Than or Equal (lte)

"discount lte 50"// discount <= 50"temperature lte 32.0"// temperature <= 32.0

Numeric Type Handling: The library automatically handles different numeric types:

vardata=new{intValue=25,doubleValue=25.0,floatValue=25.0f,decimalValue=25.0m};// All these return true"intValue eq 25""doubleValue eq 25""floatValue eq 25""decimalValue eq 25"

Logical Operators

AND Operator (and)

Requires all conditions to be true. Has higher precedence than OR.

// Simple AND"age gte 18 and isActive eq true"// Multiple AND conditions"status eq `active` and role eq `admin` and score gt 80"// Mixed with comparison operators"price gte 10 and price lte 100 and category eq `electronics`"

OR Operator (or)

Requires at least one condition to be true. Has lower precedence than AND.

// Simple OR"role eq `admin` or role eq `moderator`"// Multiple OR conditions"status eq `premium` or status eq `vip` or status eq `gold`"// Mixed with AND (AND has precedence)"isActive eq true and role eq `admin` or status eq `override`"// Evaluates as: (isActive eq true and role eq 'admin') or status eq 'override'

Operator Precedence

The evaluation order follows these precedence rules:

  1. Parentheses (highest precedence)
  2. Comparison operators (eq, not, in, gt, gte, lt, lte)
  3. AND operator
  4. OR operator (lowest precedence)

Examples:

// Without parentheses"a eq 1 or b eq 2 and c eq 3"// Evaluates as: a eq 1 or (b eq 2 and c eq 3)// With parentheses for different grouping"(a eq 1 or b eq 2) and c eq 3"// Evaluates as: (a eq 1 or b eq 2) and c eq 3

Special Cases and Edge Conditions

Null Handling

vardata=new{nullableField=(string)null};"nullableField eq `test`"// Returns false"nullableField not `test`"// Returns true

Empty Collections

vardata=new{tags=newstring[0]};"tags in (`anything`)"// Returns false

Type Mismatches

vardata=new{textNumber="25",realNumber=25};"textNumber eq 25"// Returns true (automatic conversion)"textNumber gt 20"// Returns true (automatic conversion)

Advanced Usage

Complex Nested Expressions

JSONPredicate excels at handling complex logical expressions with multiple levels of nesting:

varcustomer=new{profile=new{name="Alice Johnson",age=28,membership="premium",verified=true},account=new{balance=1500.50,currency="USD",status="active",lastTransaction=DateTime.Parse("2024-07-15T14:30:00Z")},preferences=new{notifications=true,theme="dark",language="en-US"},tags=new[]{"vip","early-adopter","premium"}};// Complex eligibility checkboolisEligibleForOffer=JSONPredicate.Evaluate(@" (profile.age gte 18 and profile.age lte 65) and  (profile.membership in (`premium`, `vip`, `gold`)) and (account.balance gt 1000 and account.status eq `active`) and (account.lastTransaction gt `2024-06-01` and profile.verified eq true)",customer);// Multi-criteria filtering with fallbacksboolhasSpecialAccess=JSONPredicate.Evaluate(@" (profile.membership eq `premium` and account.balance gt 5000) or (tags in (`vip`, `beta-tester`) and profile.verified eq true) or (profile.age gt 65 and account.status eq `active`)",customer);

Working with Arrays and Collections

Array Containment Patterns

varuser=new{roles=new[]{"user","admin","moderator"},permissions=new[]{"read","write","delete","admin"},tags=new[]{"premium","verified","early-adopter"},favoriteCategories=new[]{"electronics","books","home"}};// Check for specific roleboolisAdmin=JSONPredicate.Evaluate("roles in (`admin`)",user);// Check for any admin permissionsboolhasAdminPerms=JSONPredicate.Evaluate("permissions in (`admin`, `super-admin`)",user);// Check for multiple tag requirementsboolisPremiumUser=JSONPredicate.Evaluate("tags in (`premium`, `vip`) and roles in (`user`, `admin`)",user);// Complex array operationsboolcanAccessFeature=JSONPredicate.Evaluate(@" (roles in (`admin`, `moderator`) and permissions in (`read`, `write`)) or (tags in (`premium`, `vip`) and permissions in (`read`))",user);

Array Intersection Logic

When both sides of an in operation are arrays, the operation checks for intersection:

vardata=new{userCategories=new[]{"electronics","books","sports"},allowedCategories=new[]{"electronics","home","garden"},blockedCategories=new[]{"adult","gambling"}};// Check if user has access to any allowed categoriesboolhasAccess=JSONPredicate.Evaluate("userCategories in allowedCategories",data);// true (electronics matches)// Check if user is accessing blocked contentboolisBlocked=JSONPredicate.Evaluate("userCategories in blockedCategories",data);// false (no intersection)

DateTime Operations and Formats

JSONPredicate provides robust DateTime handling with support for multiple formats:

Supported DateTime Formats

varevents=new{createdAt=DateTime.Parse("2024-08-01T10:30:00Z"),updatedAt="2024-08-02T15:45:00Z",// String will be parsedscheduledFor="2024-08-10",deadline="2024-12-31T23:59:59Z"};// ISO 8601 with timezone"createdAt eq `2024-08-01T10:30:00Z`"// Date only format"scheduledFor eq `2024-08-10`"// Different timezone representations"createdAt eq `2024-08-01T05:30:00-05:00`"// EST timezone// Comparison operations"createdAt gt `2024-07-01` and deadline lt `2025-01-01`"

DateTime Range Queries

// Date range filteringboolinDateRange=JSONPredicate.Evaluate(@" createdAt gte `2024-08-01` and  createdAt lte `2024-08-31`",events);// Business hours checkvartransaction=new{timestamp=DateTime.Parse("2024-08-01T14:30:00Z"),amount=250.00};boolduringBusinessHours=JSONPredicate.Evaluate(@" timestamp gte `2024-08-01T09:00:00Z` and  timestamp lte `2024-08-01T17:00:00Z`",transaction);

Relative Date Comparisons

vardocument=new{createdAt=DateTime.Parse("2024-01-15T10:00:00Z"),modifiedAt=DateTime.Parse("2024-07-20T14:30:00Z"),expiresAt=DateTime.Parse("2024-12-31T23:59:59Z")};// Check if document was created this yearboolthisYear=JSONPredicate.Evaluate("createdAt gte `2024-01-01`",document);// Check if recently modified (within last 30 days of a reference point)boolrecentlyModified=JSONPredicate.Evaluate("modifiedAt gte `2024-07-01`",document);

Type Coercion and Conversion

JSONPredicate automatically handles type conversions for seamless comparisons:

Numeric Conversions

varmixedData=new{stringNumber="42",intNumber=42,floatNumber=42.0f,doubleNumber=42.0,decimalNumber=42.0m};// All these expressions return true due to automatic conversionboolresult1=JSONPredicate.Evaluate("stringNumber eq 42",mixedData);boolresult2=JSONPredicate.Evaluate("intNumber eq 42.0",mixedData);boolresult3=JSONPredicate.Evaluate("floatNumber eq 42",mixedData);boolresult4=JSONPredicate.Evaluate("stringNumber eq floatNumber",mixedData);

String and Character Handling

vartextData=new{singleChar='A',charString="A",name="John",upperName="JOHN"};// Case-insensitive string comparisonsboolmatch1=JSONPredicate.Evaluate("name eq `john`",textData);// trueboolmatch2=JSONPredicate.Evaluate("name eq `JOHN`",textData);// true// Character-string interoperabilityboolmatch3=JSONPredicate.Evaluate("singleChar eq `A`",textData);// trueboolmatch4=JSONPredicate.Evaluate("charString eq singleChar",textData);// true

Boolean Conversions

varflags=new{isActive=true,isEnabled="true",isValid=1,// Truthy valueisFlagged=false};// Boolean comparisons with type conversionboolresult=JSONPredicate.Evaluate("isActive eq true",flags);

Performance Optimization Patterns

Expression Caching Strategy

For applications that repeatedly evaluate the same expressions, consider implementing a caching strategy:

publicclassPredicateCache{privatereadonlyDictionary<string,CompiledPredicate>_cache=new();publicboolEvaluate(stringexpression,objectdata){// In a real implementation, you might want to compile expressions// for better performance on repeated evaluationsreturnJSONPredicate.Evaluate(expression,data);}}

Efficient Expression Design

Structure your expressions for optimal performance:

// Good: More selective conditions first"isActive eq true and role eq `admin` and complexCalculation gt 100"// Better: Short-circuit on most selective condition"role eq `admin` and isActive eq true and complexCalculation gt 100"// Good: Use specific comparisons when possible"status eq `active`"// Better than "status not `inactive`"

Best Practices

Expression Design Guidelines

1. Use Clear and Descriptive JSONPaths

// Good: Clear property navigation"user.profile.contactInfo.email eq `john@example.com`"// Avoid: Unclear abbreviated paths"u.p.ci.e eq `john@example.com`"

2. Consistent Value Quoting

Choose a quoting style and stick with it throughout your application:

// Good: Consistent backtick usage"name eq `John` and city eq `New York`"// Avoid: Mixed quoting styles"name eq `John` and city eq 'New York'"

3. Logical Expression Structure

Structure complex expressions for readability:

// Good: Logical grouping with clear precedence"(user.isActive eq true and user.role eq `admin`) or user.permissions in (`override`)"// Good: Multi-line for complex expressionsstringcomplexRule=@" (account.balance gt 1000 and account.status eq `active`) and (user.verified eq true and user.age gte 18) and (subscription.tier in (`premium`, `enterprise`))";

4. Performance-Conscious Design

// Good: Most selective condition first"user.role eq `admin` and user.isActive eq true and user.lastLogin gt `2024-01-01`"// Good: Use positive conditions when possible"status eq `active`"// Instead of "status not `inactive`"

Error Handling Patterns

1. Expression Validation

publicclassPredicateValidator{publicboolTryValidateExpression(stringexpression,outstringerror){try{// Test with dummy object to validate syntaxvartestObj=new{test="value"};JSONPredicate.Evaluate("test eq `value`",testObj);error=null;returntrue;}catch(ArgumentExceptionex){error=ex.Message;returnfalse;}}}

2. Safe Evaluation Pattern

publicstaticclassSafeJSONPredicate{publicstaticboolTryEvaluate(stringexpression,objectdata,outboolresult){try{result=JSONPredicate.Evaluate(expression,data);returntrue;}catch(Exception){result=false;returnfalse;}}publicstaticboolEvaluateWithDefault(stringexpression,objectdata,booldefaultValue=false){try{returnJSONPredicate.Evaluate(expression,data);}catch(Exception){returndefaultValue;}}}

Security Considerations

1. Input Sanitization

When accepting user-provided expressions, implement proper validation:

publicclassExpressionSanitizer{privatereadonlyHashSet<string>_allowedOperators=newHashSet<string>{"eq","not","in","gt","gte","lt","lte","and","or"};privatereadonlyHashSet<string>_allowedPaths=newHashSet<string>{"user.name","user.age","user.role","account.balance"// Define allowed JSONPaths};publicboolIsValidExpression(stringexpression){// Implement validation logic here// Check for allowed operators and paths onlyreturntrue;// Simplified for example}}

2. Path Restriction

Limit which properties can be accessed:

publicclassRestrictedJSONPredicate{privatereadonlyHashSet<string>_allowedPaths;publicRestrictedJSONPredicate(IEnumerable<string>allowedPaths){_allowedPaths=newHashSet<string>(allowedPaths);}publicboolEvaluate(stringexpression,objectdata){// Extract paths from expression and validatevarpaths=ExtractPaths(expression);if(paths.Any(p =>!_allowedPaths.Contains(p))){thrownewUnauthorizedAccessException("Expression contains restricted paths");}returnJSONPredicate.Evaluate(expression,data);}privateIEnumerable<string>ExtractPaths(stringexpression){// Implementation to extract JSONPaths from expression// This is a simplified examplereturnnewList<string>();}}

Testing Strategies

1. Unit Testing Expressions

[TestFixture]publicclassBusinessRuleTests{privatereadonlyobject_testCustomer=new{profile=new{name="John Doe",age=35,membershipLevel="gold"},account=new{balance=2500.00,status="active"},preferences=new{emailNotifications=true}};[Test]publicvoidPremiumCustomerRule_ShouldReturnTrue_WhenCriteriaMet(){varrule="profile.age gte 18 and account.balance gt 1000 and profile.membershipLevel in (`gold`, `platinum`)";varresult=JSONPredicate.Evaluate(rule,_testCustomer);Assert.That(result,Is.True);}[TestCase("profile.age",35,true)][TestCase("profile.age",17,false)]publicvoidAgeValidation_ShouldWorkCorrectly(stringpath,intage,boolexpected){varcustomer=new{profile=new{age=age}};varrule="profile.age gte 18";varresult=JSONPredicate.Evaluate(rule,customer);Assert.That(result,Is.EqualTo(expected));}}

2. Expression Coverage Testing

publicclassExpressionCoverageHelper{publicstaticvoidTestAllOperators(objecttestData){varoperators=new[]{"eq","not","in","gt","gte","lt","lte"};varlogicalOps=new[]{"and","or"};foreach(varopinoperators){// Test each operator with various data typesTestOperator(op,testData);}}privatestaticvoidTestOperator(stringop,objectdata){// Implementation to systematically test operators}}

Performance Considerations

Evaluation Performance

Benchmarking Results

Based on internal testing, JSONPredicate provides excellent performance characteristics:

Expression TypeEvaluations/secMemory Usage
Simple equality~2,000,000< 1KB
Complex logical~500,000< 2KB
Array operations~300,000< 3KB
DateTime comparisons~400,000< 2KB

Performance Optimization Tips

  1. Order Conditions by Selectivity

    // Good: Most selective first"user.role eq `admin` and user.isActive eq true"// Less optimal: Less selective first"user.isActive eq true and user.role eq `admin`"
  2. Use Positive Conditions

    // Preferred"status eq `active`"// Less efficient"status not `inactive`"
  3. Minimize Deep Nesting

    // Consider flattening very deep paths"user.profile.settings.preferences.theme eq `dark`"

Memory Usage Patterns

Object Serialization Overhead

JSONPredicate serializes objects to JSON for evaluation. Consider these patterns:

// Good: Lightweight evaluation objectsvarslimData=new{userId=user.Id,role=user.Role,isActive=user.IsActive};boolresult=JSONPredicate.Evaluate("role eq `admin`",slimData);// Less optimal: Heavy objects with unnecessary databoolresult2=JSONPredicate.Evaluate("role eq `admin`",fullUserObjectWithManyProperties);

Caching Strategies

For repeated evaluations, consider implementing caching:

publicclassCachedPredicateEvaluator{privatereadonlyLRUCache<string,object>_objectCache=new(1000);publicboolEvaluate(stringexpression,objectdata){stringdataKey=GenerateKey(data);if(!_objectCache.TryGetValue(dataKey,outobjectcachedData)){cachedData=data;_objectCache[dataKey]=cachedData;}returnJSONPredicate.Evaluate(expression,cachedData);}}

Scalability Considerations

High-Volume Scenarios

For applications processing thousands of evaluations per second:

publicclassHighPerformancePredicateService{privatereadonlyConcurrentDictionary<string,CompiledExpression>_expressionCache=new();publicasyncTask<bool[]>EvaluateBatchAsync(stringexpression,IEnumerable<object>dataItems){vartasks=dataItems.Select(data =>Task.Run(()=>JSONPredicate.Evaluate(expression,data)));returnawaitTask.WhenAll(tasks);}publicboolEvaluateWithMetrics(stringexpression,objectdata,outTimeSpanduration){varstopwatch=Stopwatch.StartNew();varresult=JSONPredicate.Evaluate(expression,data);duration=stopwatch.Elapsed;returnresult;}}

Memory-Efficient Patterns

// Pattern: Use projection for large objectspublicstaticclassEfficientEvaluation{publicstaticboolEvaluateProjected<T>(stringexpression,Tsource,Func<T,object>projector){varprojectedData=projector(source);returnJSONPredicate.Evaluate(expression,projectedData);}}// Usagevarresult=EfficientEvaluation.EvaluateProjected("name eq `John` and age gt 18",fullUserObject,
user =>new{name=user.FullName,age=user.Age});

API Reference

JSONPredicate Class

The main entry point for evaluating predicate expressions.

Methods

Evaluate(string expression, object obj)

Evaluates a predicate expression against an object.

Parameters:

  • expression (string): The predicate expression to evaluate
  • obj (object): The object to evaluate the expression against

Returns:

  • bool: True if the expression evaluates to true, false otherwise

Exceptions:

  • ArgumentException: Thrown when the expression format is invalid
  • FormatException: Thrown when DateTime parsing fails
  • InvalidOperationException: Thrown for unsupported operations

Example:

vardata=new{name="John",age=25};boolresult=JSONPredicate.Evaluate("name eq `John` and age gte 18",data);

Expression Grammar

The formal grammar for JSONPredicate expressions:

Expression := OrExpression
OrExpression := AndExpression ('or' AndExpression)*
AndExpression := AtomicExpression ('and' AtomicExpression)*
AtomicExpression := '(' Expression ')'
| JSONPath Operator Value
JSONPath := Identifier ('.' Identifier)*
Operator := 'eq' | 'not' | 'in' | 'gt' | 'gte' | 'lt' | 'lte'
Value := StringLiteral | NumberLiteral | BooleanLiteral | ArrayLiteral
StringLiteral := '`' [^`]* '`' | '\'' [^']* '\'' | '"' [^"]* '"'
NumberLiteral := '-'? [0-9]+ ('.' [0-9]+)?
BooleanLiteral := 'true' | 'false'
ArrayLiteral := '(' Value (',' Value)* ')'
Identifier := [a-zA-Z_][a-zA-Z0-9_]*

Internal Components

While these are internal implementation details, understanding them can help with troubleshooting:

DataTypes Class

Handles type comparisons and conversions.

JsonPath Class

Manages JSONPath navigation and property resolution.


Examples

Real-World Use Cases

1. User Authentication and Authorization

publicclassAuthService{publicboolIsAuthorized(Useruser,stringresource,stringaction){varcontext=new{user=new{id=user.Id,role=user.Role,permissions=user.Permissions.ToArray(),isActive=user.IsActive,accountExpiry=user.AccountExpiry},resource=resource,action=action,currentTime=DateTime.UtcNow};// Admin overrideif(JSONPredicate.Evaluate("user.role eq `admin` and user.isActive eq true",context))returntrue;// Check specific permissionsvarpermissionRule=$"user.permissions in (`{resource}:{action}`, `{resource}:*`, `*:*`)";if(JSONPredicate.Evaluate($"{permissionRule} and user.isActive eq true",context))returntrue;// Account expiry checkif(JSONPredicate.Evaluate("user.accountExpiry lt currentTime",context))returnfalse;returnfalse;}}

2. E-commerce Product Filtering

publicclassProductFilterService{publicIEnumerable<Product>FilterProducts(IEnumerable<Product>products,ProductFilterCriteriacriteria){varfilterExpression=BuildFilterExpression(criteria);returnproducts.Where(product =>{varproductData=new{name=product.Name,price=product.Price,category=product.Category,brand=product.Brand,rating=product.AverageRating,inStock=product.StockQuantity>0,tags=product.Tags.ToArray(),releaseDate=product.ReleaseDate,onSale=product.SalePrice.HasValue};returnJSONPredicate.Evaluate(filterExpression,productData);});}privatestringBuildFilterExpression(ProductFilterCriteriacriteria){varconditions=newList<string>();if(criteria.MinPrice.HasValue)conditions.Add($"price gte {criteria.MinPrice.Value}");if(criteria.MaxPrice.HasValue)conditions.Add($"price lte {criteria.MaxPrice.Value}");if(!string.IsNullOrEmpty(criteria.Category))conditions.Add($"category eq `{criteria.Category}`");if(criteria.InStockOnly)conditions.Add("inStock eq true");if(criteria.MinRating.HasValue)conditions.Add($"rating gte {criteria.MinRating.Value}");if(criteria.Tags?.Any()==true){vartagsList=string.Join("`, `",criteria.Tags);conditions.Add($"tags in (`{tagsList}`)");}if(criteria.OnSaleOnly)conditions.Add("onSale eq true");returnconditions.Any()?string.Join(" and ",conditions):"inStock eq true";}}

3. Business Rule Engine

publicclassBusinessRuleEngine{privatereadonlyDictionary<string,string>_rules=new(){["PREMIUM_ELIGIBILITY"]=@" (customer.age gte 21 and customer.creditScore gt 700) and (account.balance gt 10000 or account.monthlyIncome gt 5000) and customer.accountAge gte 365",["DISCOUNT_ELIGIBILITY"]=@" (customer.loyaltyTier in (`gold`, `platinum`) and order.amount gt 100) or (customer.isFirstTime eq true and order.amount gt 50) or (customer.tags in (`employee`, `partner`) and order.amount gt 0)",["FRAUD_DETECTION"]=@" (transaction.amount gt customer.averageTransactionAmount * 10) or (transaction.location not customer.usualLocations) or (transaction.time lt `06:00` or transaction.time gt `23:00`) and (transaction.amount gt 1000)"};publicboolEvaluateRule(stringruleName,objectcontext){if(!_rules.TryGetValue(ruleName,outvarexpression))thrownewArgumentException($"Unknown rule: {ruleName}");returnJSONPredicate.Evaluate(expression,context);}publicDictionary<string,bool>EvaluateAllRules(objectcontext){return_rules.ToDictionary(
kvp =>kvp.Key,
kvp =>JSONPredicate.Evaluate(kvp.Value,context));}}

4. Configuration-Driven Validation

publicclassConfigurableValidator{publicclassValidationRule{publicstringName{get;set;}publicstringExpression{get;set;}publicstringErrorMessage{get;set;}publicboolIsWarning{get;set;}}privatereadonlyList<ValidationRule>_rules=new(){newValidationRule{Name="AGE_VALIDATION",Expression="user.age gte 18 and user.age lte 120",ErrorMessage="User age must be between 18 and 120",IsWarning=false},newValidationRule{Name="EMAIL_DOMAIN_CHECK",Expression="user.email in (`@company.com`, `@partner.com`)",ErrorMessage="Only company or partner email addresses are allowed",IsWarning=true}};publicValidationResultValidate(objectdata){varresult=newValidationResult{IsValid=true};foreach(varrulein_rules){try{boolisValid=JSONPredicate.Evaluate(rule.Expression,data);if(!isValid){if(rule.IsWarning){result.Warnings.Add(rule.ErrorMessage);}else{result.Errors.Add(rule.ErrorMessage);result.IsValid=false;}}}catch(Exceptionex){result.Errors.Add($"Rule '{rule.Name}' evaluation failed: {ex.Message}");result.IsValid=false;}}returnresult;}}publicclassValidationResult{publicboolIsValid{get;set;}publicList<string>Errors{get;set;}=new();publicList<string>Warnings{get;set;}=new();}

5. Dynamic Content Personalization

publicclassContentPersonalizationService{privatereadonlyDictionary<string,ContentRule>_contentRules=new(){["SHOW_PREMIUM_CTA"]=newContentRule{Expression="user.tier eq `free` and user.usageDays gt 7 and user.featuresUsed gt 3",Content="Upgrade to Premium for unlimited access!"},["SHOW_RENEWAL_NOTICE"]=newContentRule{Expression="user.subscriptionExpiry lt `2024-12-31` and user.tier in (`premium`, `pro`)",Content="Your subscription expires soon. Renew now!"},["SHOW_WELCOME_MESSAGE"]=newContentRule{Expression="user.isNewUser eq true and user.lastLogin eq null",Content="Welcome! Let's get you started."}};publicList<string>GetPersonalizedContent(Useruser){varuserContext=new{user=new{id=user.Id,tier=user.SubscriptionTier,isNewUser=user.CreatedAt>DateTime.UtcNow.AddDays(-7),lastLogin=user.LastLoginAt,subscriptionExpiry=user.SubscriptionExpiry,usageDays=(DateTime.UtcNow-user.CreatedAt).Days,featuresUsed=user.FeatureUsageCount}};varapplicableContent=newList<string>();foreach(varrulein_contentRules){if(JSONPredicate.Evaluate(rule.Value.Expression,userContext)){applicableContent.Add(rule.Value.Content);}}returnapplicableContent;}privateclassContentRule{publicstringExpression{get;set;}publicstringContent{get;set;}}}

Integration Examples

ASP.NET Core Integration

// Startup.cs or Program.cspublicvoidConfigureServices(IServiceCollectionservices){services.AddScoped<IPredicateService,PredicateService>();}// Controller[ApiController][Route("api/[controller]")]publicclassUsersController:ControllerBase{privatereadonlyIPredicateService_predicateService;publicUsersController(IPredicateServicepredicateService){_predicateService=predicateService;}[HttpGet("filter")]publicIActionResultFilterUsers([FromQuery]stringexpression){if(string.IsNullOrEmpty(expression))returnBadRequest("Expression is required");try{varusers=GetUsers();// Your data sourcevarfilteredUsers=users.Where(user =>_predicateService.EvaluateUserFilter(expression,user));returnOk(filteredUsers);}catch(ArgumentExceptionex){returnBadRequest($"Invalid expression: {ex.Message}");}}}

Entity Framework Integration

publicstaticclassQueryableExtensions{publicstaticIQueryable<T>WherePredicate<T>(thisIQueryable<T>source,stringexpression){returnsource.AsEnumerable().Where(item =>JSONPredicate.Evaluate(expression,item)).AsQueryable();}// For in-memory evaluation after database querypublicstaticIEnumerable<T>FilterWithPredicate<T>(thisIEnumerable<T>source,stringexpression){returnsource.Where(item =>JSONPredicate.Evaluate(expression,item));}}// UsagevaractiveAdminUsers=dbContext.Users.Where(u =>u.IsActive)// Database filter.ToList()// Execute database query.FilterWithPredicate("role eq `admin` and lastLogin gt `2024-01-01`");// In-memory predicate

Background Service Integration

publicclassRuleEvaluationService:BackgroundService{privatereadonlyIServiceProvider_serviceProvider;privatereadonlyILogger<RuleEvaluationService>_logger;publicRuleEvaluationService(IServiceProviderserviceProvider,ILogger<RuleEvaluationService>logger){_serviceProvider=serviceProvider;_logger=logger;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){while(!stoppingToken.IsCancellationRequested){usingvarscope=_serviceProvider.CreateScope();varbusinessRuleEngine=scope.ServiceProvider.GetRequiredService<BusinessRuleEngine>();try{awaitProcessPendingRules(businessRuleEngine);}catch(Exceptionex){_logger.LogError(ex,"Error processing business rules");}awaitTask.Delay(TimeSpan.FromMinutes(5),stoppingToken);}}privateasyncTaskProcessPendingRules(BusinessRuleEngineruleEngine){varpendingEvaluations=GetPendingEvaluations();foreach(varevaluationinpendingEvaluations){varresult=ruleEngine.EvaluateRule(evaluation.RuleName,evaluation.Context);awaitProcessRuleResult(evaluation,result);}}}

Troubleshooting

Common Issues and Solutions

1. Expression Format Errors

Problem:ArgumentException: Invalid expression format

Common Causes:

  • Missing operators
  • Incorrect syntax
  • Unmatched parentheses

Solutions:

// ❌ Incorrect"user.name John"// Missing operator// ✅ Correct"user.name eq `John`"// ❌ Incorrect"user.age > 18"// Invalid operator// ✅ Correct"user.age gt 18"// ❌ Incorrect"(user.age gte 18 and user.isActive eq true"// Unmatched parenthesis// ✅ Correct"(user.age gte 18 and user.isActive eq true)"

2. Type Conversion Issues

Problem: Unexpected comparison results

Common Causes:

  • Type mismatches
  • String/numeric confusion
  • DateTime format issues

Solutions:

// Issue: String numbers not comparing correctlyvardata=new{count="25"};// ❌ This might not work as expected in some cases"count gt 20"// ✅ Better approach - be explicit about types// The library handles this automatically, but be aware of your data types// Issue: DateTime format problems// ❌ Incorrect format"createdAt gt `2024-13-01`"// Invalid month// ✅ Correct format"createdAt gt `2024-01-01T00:00:00Z`"

3. JSONPath Resolution Issues

Problem: Properties not found or incorrect navigation

Common Causes:

  • Incorrect property names
  • Case sensitivity issues
  • Missing nested properties

Solutions:

vardata=new{User=new{// Note: Capital 'U'Name="John"// Note: Capital 'N'}};// ❌ Incorrect casing"user.name eq `John`"// Returns false - property not found// ✅ Correct casing"User.Name eq `John`"// Returns true// ❌ Non-existent path"user.profile.name eq `John`"// Returns false if profile doesn't exist// ✅ Check your object structure
Console.WriteLine(JsonSerializer.Serialize(data,newJsonSerializerOptions{WriteIndented=true}));

4. Array Operation Confusion

Problem:in operator not working as expected

Common Causes:

  • Misunderstanding of array intersection logic
  • Incorrect value syntax

Solutions:

vardata=new{tags=new[]{"admin","user"}};// ❌ Incorrect - missing parentheses for multiple values"tags in `admin`, `user`"// ✅ Correct"tags in (`admin`, `user`)"// ❌ Incorrect - trying to check if string contains arrayvarsingleTag=new{role="admin"};"role in tags"// This won't work - tags doesn't exist in singleTag// ✅ Correct - check if single value is in array"role in (`admin`, `user`, `moderator`)"

Debugging Techniques

1. Expression Breakdown

publicstaticclassPredicateDebugger{publicstaticvoidDebugExpression(stringexpression,objectdata){Console.WriteLine($"Expression: {expression}");Console.WriteLine($"Data: {JsonSerializer.Serialize(data,newJsonSerializerOptions{WriteIndented=true})}");try{varresult=JSONPredicate.Evaluate(expression,data);Console.WriteLine($"Result: {result}");}catch(Exceptionex){Console.WriteLine($"Error: {ex.Message}");}}publicstaticvoidTestExpressionParts(stringexpression,objectdata){// Break down complex expressions into partsvarparts=SplitExpression(expression);foreach(varpartinparts){try{varresult=JSONPredicate.Evaluate(part,data);Console.WriteLine($"'{part}' -> {result}");}catch(Exceptionex){Console.WriteLine($"'{part}' -> ERROR: {ex.Message}");}}}}

2. Data Structure Inspection

publicstaticclassDataInspector{publicstaticvoidInspectObject(objectobj){varjson=JsonSerializer.Serialize(obj,newJsonSerializerOptions{WriteIndented=true,PropertyNamingPolicy=JsonNamingPolicy.CamelCase});Console.WriteLine("Object structure:");Console.WriteLine(json);// Extract all possible JSONPathsvarpaths=ExtractAllPaths(obj);Console.WriteLine("\nAvailable JSONPaths:");foreach(varpathinpaths){Console.WriteLine($" {path}");}}privatestaticList<string>ExtractAllPaths(objectobj,stringprefix=""){varpaths=newList<string>();varjson=JsonSerializer.Serialize(obj);usingvardoc=JsonDocument.Parse(json);ExtractPathsRecursive(doc.RootElement,prefix,paths);returnpaths;}privatestaticvoidExtractPathsRecursive(JsonElementelement,stringcurrentPath,List<string>paths){switch(element.ValueKind){caseJsonValueKind.Object:foreach(varpropinelement.EnumerateObject()){varnewPath=string.IsNullOrEmpty(currentPath)?prop.Name:$"{currentPath}.{prop.Name}";paths.Add(newPath);ExtractPathsRecursive(prop.Value,newPath,paths);}break;}}}

3. Performance Profiling

publicstaticclassPerformanceProfiler{publicstaticvoidProfileExpression(stringexpression,objectdata,intiterations=1000){// Warmupfor(inti=0;i<100;i++){JSONPredicate.Evaluate(expression,data);}varstopwatch=Stopwatch.StartNew();for(inti=0;i<iterations;i++){JSONPredicate.Evaluate(expression,data);}stopwatch.Stop();Console.WriteLine($"Expression: {expression}");Console.WriteLine($"Iterations: {iterations}");Console.WriteLine($"Total time: {stopwatch.ElapsedMilliseconds}ms");Console.WriteLine($"Average time: {(double)stopwatch.ElapsedTicks/iterations/TimeSpan.TicksPerMillisecond:F4}ms");Console.WriteLine($"Evaluations per second: {iterations/stopwatch.Elapsed.TotalSeconds:F0}");}}

Error Message Guide

Error MessageCauseSolution
"Invalid expression format: ..."Syntax error in expressionCheck expression syntax, ensure proper operator usage
"Unable to parse DateTime value: ..."Invalid DateTime formatUse ISO 8601 format (YYYY-MM-DDTHH:mm:ssZ)
"Comparison operator not supported"Unknown operatorUse only: eq, not, in, gt, gte, lt, lte
"Property not found: ..."JSONPath doesn't existVerify object structure and property names

Contributing

Development Setup

  1. Clone the repository

    git clone https://github.com/CodeShayk/JSONPredicate.git
    cd JSONPredicate
  2. Install dependencies

    dotnet restore
  3. Build the solution

    dotnet build
  4. Run tests

    dotnet test

Contribution Guidelines

Code Standards

  1. Follow C# coding conventions
  2. Use meaningful variable and method names
  3. Add XML documentation for public APIs
  4. Include unit tests for new features
  5. Maintain backward compatibility

Testing Requirements

All contributions must include comprehensive tests:

[TestFixture]publicclassNewFeatureTests{[Test]publicvoidNewFeature_ValidInput_ShouldReturnExpectedResult(){// ArrangevartestData=new{/* test object */};varexpression="/* your expression */";// Actvarresult=JSONPredicate.Evaluate(expression,testData);// AssertAssert.That(result,Is.True);}[Test]publicvoidNewFeature_InvalidInput_ShouldThrowException(){// Test error conditions}[TestCase(/* parameters */)]publicvoidNewFeature_ParameterizedTests(/* parameters */){// Parameterized test cases}}

Pull Request Process

  1. Create a feature branch

    git checkout -b feature/your-feature-name
  2. Make your changes with tests

  3. Ensure all tests pass

    dotnet test --verbosity normal
  4. Update documentation if needed

  5. Submit pull request with clear description

Reporting Issues

When reporting bugs, please include:

  1. Environment details (.NET version, OS)
  2. Minimal reproduction case
  3. Expected vs actual behavior
  4. Error messages or stack traces
// Example bug report templatevartestData=new{/* minimal object */};varexpression="/* problematic expression */";// Expected: true// Actual: false (or exception)varresult=JSONPredicate.Evaluate(expression,testData);

FAQ

General Questions

Q: What's the performance impact of using JSONPredicate? A: JSONPredicate is highly optimized for performance. Simple expressions evaluate at ~2M ops/second, complex expressions at ~500K ops/second. The main overhead is JSON serialization, so consider using lightweight projection objects for high-volume scenarios.

Q: Can I use JSONPredicate with Entity Framework? A: JSONPredicate works with in-memory collections. For Entity Framework, first execute your database query, then apply JSONPredicate filters to the results in memory.

Q: Is JSONPredicate thread-safe? A: Yes, JSONPredicate is stateless and thread-safe. You can safely call JSONPredicate.Evaluate() from multiple threads simultaneously.

Q: What's the maximum expression complexity supported? A: There's no hard limit on expression complexity. However, very deep nesting or extremely long expressions may impact performance. Consider breaking complex rules into smaller, composed expressions.

Technical Questions

Q: How does type conversion work? A: JSONPredicate automatically converts between compatible types:

  • String numbers to numeric types for comparisons
  • Case-insensitive string comparisons
  • DateTime string parsing with multiple format support
  • Boolean value interpretation

Q: Can I extend JSONPredicate with custom operators? A: The current version doesn't support custom operators. This is a planned feature for future releases. Consider using composition of existing operators for now.

Q: How are null values handled? A: Null values are handled gracefully:

  • null eq null returns true
  • null eq "anything" returns false
  • Missing properties are treated as null

Q: What JSONPath features are supported? A: Currently supports:

  • Property navigation with dot notation (object.property)
  • Nested object access (object.nested.property)

Not yet supported:

  • Array indexing (array[0])
  • Wildcards (object.*)
  • Recursive descent (object..property)

Best Practices Questions

Q: How should I structure complex business rules? A: Consider these patterns:

  1. Use a rule engine pattern with named rules
  2. Break complex expressions into smaller, testable parts
  3. Use configuration files for business rules
  4. Implement rule versioning for changes over time

Q: How do I handle user-provided expressions safely? A: Implement validation layers:

  1. Whitelist allowed JSONPaths
  2. Validate expression syntax before execution
  3. Use try-catch patterns for graceful error handling
  4. Consider rate limiting for user-provided expressions

Q: What's the best way to debug failing expressions? A: Use these debugging techniques:

  1. Break complex expressions into parts
  2. Inspect your data structure with JSON serialization
  3. Test individual conditions separately
  4. Use the debugging utilities provided in the troubleshooting section

The library provides a powerful, flexible way to evaluate complex conditional logic against JSON objects, making it ideal for business rules, filtering, validation, and many other use cases in .NET applications.

Clone this wiki locally