Overview
JsonQL can be extended in several ways:
Custom Functions: Add new functions like
MyCustomFunction()Custom Aggregate Functions: Add new aggregate functions like:
CountEvenIndexes(collection, predicate)Custom Path Functions: Create collection operators like
WhereorSelectCustom Operators: Define new operators: binary, unary prefix and unary postfix (e.g.,
~=for regex matching)Custom Interface Implementations: Override default C# type implementations for JSON-to-object conversion
Custom Expression Language: Modify the JsonQL language itself
Best Practices
Error Handling: Always return proper
IParseResultwith errors when validation failsLine Information: Pass
IJsonLineInfothrough for accurate error reportingType Safety: Use appropriate base classes (
StringJsonFunctionAbstr,DateTimeJsonFunctionAbstr, etc.)Null Handling: Check for null values and handle appropriately
Parameter Validation: Validate function parameters before use
Documentation: Document your custom functions with XML comments
Testing: Write unit tests for custom extensions
Performance: Consider caching expensive operations
Thread Safety: Ensure custom implementations are thread-safe
Naming: Use clear, descriptive names that don’t conflict with built-in functions
Common Patterns
Note
Couple of common patterns are demonstrated here, however this does not cover all patterns for extensions. Look at source code to see how functions, operators are implemented in JsonQL to understand how extensions can be done, as well as look at extension examples in namespace JsonQL.Demos.CustomJsonQL.Compilation.
Pattern 1: Simple Value Function
public class MyValueFunction : StringJsonFunctionAbstr
{
public override IParseResult<string?> EvaluateStringValue(...)
{
return new ParseResult<string?>("my value");
}
}
Pattern 2: Function with Parameters
public class MyParameterizedFunction : StringJsonFunctionAbstr
{
private readonly IJsonFunction _parameter1;
private readonly IJsonFunction _parameter2;
public MyParameterizedFunction(
string functionName,
IJsonFunction parameter1,
IJsonFunction parameter2,
IJsonFunctionValueEvaluationContext context,
IJsonLineInfo? lineInfo)
: base(functionName, context, lineInfo)
{
_parameter1 = parameter1;
_parameter2 = parameter2;
}
public override IParseResult<string?> EvaluateStringValue(...)
{
var param1Result = _parameter1.EvaluateValue(...);
var param2Result = _parameter2.EvaluateValue(...);
// Process and return result
}
}
Pattern 3: Collection Path Function
public class MyCollectionFunction
: JsonValueCollectionItemsSelectorPathElementAbstr
{
protected override IParseResult<ICollectionJsonValuePathLookupResult>
SelectCollectionItems(
IReadOnlyList<IParsedValue> parentParsedValues,
IRootParsedValue rootParsedValue,
IReadOnlyList<IRootParsedValue> compiledParentRootParsedValues)
{
var filteredValues = new List<IParsedValue>();
// Process collection
foreach (var value in parentParsedValues)
{
// Apply logic
if (ShouldInclude(value))
filteredValues.Add(value);
}
return new ParseResult<ICollectionJsonValuePathLookupResult>(
new CollectionJsonValuePathLookupResult(filteredValues));
}
}
Troubleshooting
Issue: Custom Function Not Recognized
Verify function name in factory’s switch statement
Ensure factory is registered in DI container
Check that custom parameter resolver is registered
Issue: Type Conversion Errors
Use
JsonFunctionHelpers.TryConvertValueToJsonComparableCheck TypeCode parameter matches expected type
Validate input before conversion
Issue: Null Reference Exceptions
Check for null in
EvaluateValueresultsHandle
IJsonValuePathLookupResultwithHasValue == falseValidate all function parameters
See Also
Dependency Injection Setup: Setting up custom DI modules
JsonQL GitHub Repository: Source code and examples