diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2fbde97
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,28 @@
+# Changelog
+
+## [v1.2.0] - In Development
+
+### Added
+- Array indexing support in JSONPath (e.g., `array[0].property`)
+- New comparison operators: `starts_with`, `ends_with`, and `contains`
+- Comprehensive unit tests for all new features
+
+### Changed
+- Replaced JSON serialization with direct object navigation for path evaluation (50%+ performance improvement)
+- Optimized expression parsing without regex dependency
+- Fixed namespace inconsistency across library components
+- Implemented thread-safe operator dictionary to prevent race conditions
+
+### Performance
+- Direct object navigation replacing JSON serialization for path evaluation (50%+ faster)
+- Optimized expression parsing without regex
+- Reduced memory allocations by 60%+
+
+### Fixed
+- Namespace inconsistency: Changed JsonPathPredicate to JSONPredicate in supporting files
+- Thread safety in operator dictionary access
+
+## [v1.0.0] - Initial Release
+- Initial release of JSONPredicate library
+- Core functionality with basic operators
+- JSONPath-style property navigation
\ No newline at end of file
diff --git a/GitVersion.yml b/GitVersion.yml
index c967cdc..d9fd3d8 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -1,4 +1,4 @@
-next-version: 1.1.0
+next-version: 1.2.0
tag-prefix: '[vV]'
mode: ContinuousDeployment
branches:
diff --git a/README.md b/README.md
index f0b886d..8d3641f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-#
JSONPredicate v1.1.0
+#
JSONPredicate v1.2.0
[](https://badge.fury.io/nu/JSONPredicate) [](https://github.com/CodeShayk/JSONPredicate/blob/master/LICENSE.md)
[](https://github.com/CodeShayk/JSONPredicate/releases/latest)
[](https://github.com/CodeShayk/JSONPredicate/actions/workflows/Master-Build.yml)
@@ -18,7 +18,7 @@
- **Multiple Operators**: `eq` (equal), `in` (contains), `not` (not equal), `gt` (greater than), `gte` (greater than or equal), `lt` (less than), `lte` (less than or equal)
- **Logical Operators**: `and`, `or` with proper precedence handling
- **Array Handling**: Evaluate conditions on `arrays` and `collections`. Support array `indexing` (Available from v1.1.0)
-- **String Operations**: `starts_with`, `ends_with`, `contains` (Available from v1.1.0)
+- **String Operations**: `starts_with`, `ends_with`, `contains` (Available from v1.2.0)
- **Type Safety**: `Automatic` type conversion and validation
- **Complex Expressions**: `Parentheses` grouping and `nested` operations
- **Lightweight**: `Minimal` dependencies, `fast` evaluation
@@ -38,7 +38,7 @@ The expression syntax is ([JSONPath] [Comparison Operator] [Value]) [Logical Ope
#### ii. Supported Operators
- Comparison Operators - `eq`, `in`, `gt`, `gte`, `lt`, `lte` & `Not`
- Logical Operators - `and` & `or`
-- String Operators - `starts_with`, `ends_with`, `contains` (Available from v1.1.0)
+- String Operators - `starts_with`, `ends_with`, `contains` (Available from v1.2.0)
### Example
```
var customer = new {
@@ -66,13 +66,13 @@ bool result2 = JSONPredicate.Evaluate("client.address.postcode eq `e113et` and c
bool result3 = JSONPredicate.Evaluate("client.tags in [`vip`, `standard`]", customer);
bool
```
-#### iv. String operators (Available from v1.1.0)
+#### iv. String operators (Available from v1.2.0)
```
bool result4 = JSONPredicate.Evaluate("client.address.postcode starts_with `e11`", customer);
bool result5 = JSONPredicate.Evaluate("client.address.postcode ends_with `3et`", customer);
bool result6 = JSONPredicate.Evaluate("client.address.postcode contains `13`", customer);
```
-#### v. Deep Array Indexing (Available from v1.1.0)
+#### v. Deep Array Indexing (Available from v1.2.0)
```
bool result7 = JSONPredicate.Evaluate("client.tags[1] eq `premium`", customer);
```
@@ -86,7 +86,7 @@ This section provides the summary of planned releases with key details about eac
| Version | Release Date | Type | Key Improvements | Backward Compatible |
|---------|--------------|------|------------------|-------------------|
| v1.0.0 | August 2025 | Major | Initial release with core predicate evaluation | N/A |
-| v1.1.0 | October 2025 | Minor | Namespace consistency, thread safety, 50%+ performance improvement, array indexing, new operators (`starts_with`, `ends_with`, `contains`) | Yes |
+| v1.2.0 | October 2025 | Minor | Namespace consistency, thread safety, 50%+ performance improvement, array indexing, new operators (`starts_with`, `ends_with`, `contains`), comprehensive unit tests | Yes |
| v2.0.0 | TBC | Major | Comprehensive validation, performance benchmarking, thread safety verification, complete documentation | Mostly* |
*Note: v2.0.0 marked as "Mostly" backward compatible due to major internal changes that may affect some advanced usage patterns.
diff --git a/Release_Roadmap.md b/Release_Roadmap.md
index cdb6623..f03c3c5 100644
--- a/Release_Roadmap.md
+++ b/Release_Roadmap.md
@@ -26,7 +26,7 @@ Initial release of the JSONPredicate library. Provides core functionality for ev
---
-### v1.1.0 - Combined Fix, Feature and Performance Release
+### v1.2.0 - Combined Fix, Feature and Performance Release
**Release Type**: Minor (Backward Compatible)
**Release Date**: October 2025
**Focus**: Critical fixes, new functionality and performance improvements
@@ -148,7 +148,7 @@ Major feature and performance release with comprehensive validation. This releas
## Evolution Summary
-### v1.0.0 → v1.1.0: Foundation, Stability, Performance and Features
+### v1.0.0 → v1.2.0: Foundation, Stability, Performance and Features
- **Focus**: Internal consistency, thread safety, performance optimization and new functionality
- **Improvement**: Fixed critical namespace inconsistency
- **Improvement**: Made operator dictionary thread-safe
@@ -156,16 +156,9 @@ Major feature and performance release with comprehensive validation. This releas
- **Improvement**: Expression parsing optimization removing regex dependency
- **Addition**: Array indexing support (e.g., `array[0].property`)
- **Addition**: New operators (`starts_with`, `ends_with`, `contains`)
+- **Addition**: Comprehensive unit tests for all new features
- **Impact**: Better stability, reliability and performance with new features, all backward compatible
-### v1.1.0 → v2.0.0: Validation and Production Readiness
-- **Focus**: Comprehensive validation and release preparation
-- **Achievement**: All features integrated and validated together
-- **Achievement**: Performance improvements quantitatively verified
-- **Achievement**: Thread safety comprehensively tested
-- **Achievement**: Complete documentation and packaging
-- **Impact**: Production-ready major release with all improvements validated
-
## Technical Improvements Summary
### Performance Improvements
@@ -188,21 +181,13 @@ Major feature and performance release with comprehensive validation. This releas
## Upgrade Path
-### From v1.0.0 to v1.1.0
+### From v1.0.0 to v1.2.0
- Drop-in replacement
- No code changes required for existing functionality
- Benefits: Thread safety, performance improvements, and new features available
-### From v1.0.x to v2.0.0
-- Drop-in replacement for basic usage
-- Thorough testing recommended for advanced usage due to internal implementation changes
-- Benefit: All improvements and comprehensive validation
-
## Key Metrics
| Version | Performance Improvement | Memory Improvement | New Features | Backward Compatible |
|---------|------------------------|-------------------|----------------|-------------------|
-| v1.1.0 | 50-70% | 60%+ | Namespace consistency, thread safety, array indexing, 3 new operators | Yes |
-| v2.0.0 | 50-70% (maintained) | 60%+ (maintained) | Complete validation, documentation | Mostly |
-
-*Note: v2.0.0 marked as "Mostly" backward compatible due to major internal changes that may affect some advanced usage patterns.*
+| v1.2.0 | 50-70% | 60%+ | Namespace consistency, thread safety, array indexing, 3 new operators, comprehensive unit tests | Yes |
diff --git a/src/JSONPredicate/Expression.cs b/src/JSONPredicate/Expression.cs
index 5b1537e..6785bdf 100644
--- a/src/JSONPredicate/Expression.cs
+++ b/src/JSONPredicate/Expression.cs
@@ -31,7 +31,7 @@ public static (string Path, string Operator, string Value) Parse(string expressi
throw new ArgumentException($"Invalid expression format: {expression}");
// Define operators in order of length (longer first) to avoid partial matches
- var operators = new[] { "gte", "lte", "not", "eq", "gt", "lt", "in" };
+ var operators = new[] { "gte", "lte", "not", "eq", "gt", "lt", "in", "starts_with", "ends_with", "contains" };
for (int i = 0; i < expr.Length; i++)
{
diff --git a/src/JSONPredicate/JSONPredicate.csproj b/src/JSONPredicate/JSONPredicate.csproj
index 944e42c..a6f84e4 100644
--- a/src/JSONPredicate/JSONPredicate.csproj
+++ b/src/JSONPredicate/JSONPredicate.csproj
@@ -1,4 +1,4 @@
-
+
net462;netstandard2.0;netstandard2.1;net9.0
@@ -21,7 +21,7 @@
https://github.com/CodeShayk/JSONPredicate/wiki
https://github.com/CodeShayk/JSONPredicate
- v1.1.0 - Enhanced JSONPredicate library with significant improvements.
+ v1.2.0 - Enhanced JSONPredicate library with additional improvements.
- Array indexing support in JSONPath (e.g., `array[0].property`)
- New comparison operators: `starts_with`, `ends_with`, and `contains`
- Direct object navigation with 50%+ performance improvement
@@ -29,9 +29,9 @@
- Optimized expression parsing without regex dependency.
For more details, visit the release page:
- https://github.com/CodeShayk/JSONPredicate/releases/tag/v1.1.0
+ https://github.com/CodeShayk/JSONPredicate/releases/tag/v1.2.0
- 1.1.0
+ 1.2.0
True
JSONPredicate
diff --git a/tests/JSONPredicate.Tests/PredicateTests.cs b/tests/JSONPredicate.Tests/PredicateTests.cs
index f837733..522ac90 100644
--- a/tests/JSONPredicate.Tests/PredicateTests.cs
+++ b/tests/JSONPredicate.Tests/PredicateTests.cs
@@ -566,5 +566,336 @@ public void Evaluate_InOperatorWithParenthesesAndNestedLogic_ShouldReturnTrue()
var result = JSONPredicate.Evaluate("(client.role in (`admin`, `manager`) or client.tags in (`vip`)) and client.address.active eq true", _testObject);
Assert.That(result, Is.True);
}
+
+ // === v1.1.0 NEW FEATURES TESTS ===
+
+ // Array Indexing Tests - NEW in v1.1.0
+ [Test]
+ public void Evaluate_ArrayIndexing_AccessFirstElement_ShouldReturnTrue()
+ {
+ var obj = new { items = new[] { "first", "second", "third" } };
+ var result = JSONPredicate.Evaluate("items[0] eq `first`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_AccessMiddleElement_ShouldReturnTrue()
+ {
+ var obj = new { items = new[] { "first", "second", "third" } };
+ var result = JSONPredicate.Evaluate("items[1] eq `second`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_AccessLastElement_ShouldReturnTrue()
+ {
+ var obj = new { items = new[] { "first", "second", "third" } };
+ var result = JSONPredicate.Evaluate("items[2] eq `third`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_InvalidIndex_ShouldReturnFalse()
+ {
+ var obj = new { items = new[] { "first", "second" } };
+ var result = JSONPredicate.Evaluate("items[10] eq `nonexistent`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_NegativeIndex_ShouldReturnFalse()
+ {
+ var obj = new { items = new[] { "first", "second" } };
+ var result = JSONPredicate.Evaluate("items[-1] eq `first`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_NonArrayProperty_ShouldReturnFalse()
+ {
+ var obj = new { items = "not-an-array" };
+ var result = JSONPredicate.Evaluate("items[0] eq `first`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_NestedArrayElement_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ users = new[]
+ {
+ new { name = "John", role = "admin" },
+ new { name = "Jane", role = "user" }
+ }
+ };
+ var result = JSONPredicate.Evaluate("users[0].name eq `John`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_NestedArrayElementWithComparison_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ users = new[]
+ {
+ new { name = "John", role = "admin" },
+ new { name = "Jane", role = "user" }
+ }
+ };
+ var result = JSONPredicate.Evaluate("users[1].role eq `user`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_WithLogicalOperators_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ items = new[] { "active", "pending" },
+ status = "ok"
+ };
+ var result = JSONPredicate.Evaluate("items[0] eq `active` and status eq `ok`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ArrayIndexing_WithInOperator_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ tags = new[] { "premium", "verified" }
+ };
+ var result = JSONPredicate.Evaluate("tags[0] in (`premium`, `basic`)", obj);
+ Assert.That(result, Is.True);
+ }
+
+ // StartsWith Operator Tests - NEW in v1.1.0
+ [Test]
+ public void Evaluate_StartsWithOperator_MatchBeginning_ShouldReturnTrue()
+ {
+ var obj = new { name = "John Doe" };
+ var result = JSONPredicate.Evaluate("name starts_with `John`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_NoMatch_ShouldReturnFalse()
+ {
+ var obj = new { name = "John Doe" };
+ var result = JSONPredicate.Evaluate("name starts_with `Jane`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_CaseInsensitive_ShouldReturnTrue()
+ {
+ var obj = new { name = "John Doe" };
+ var result = JSONPredicate.Evaluate("name starts_with `john`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_EmptyString_ShouldReturnTrue()
+ {
+ var obj = new { name = "John" };
+ var result = JSONPredicate.Evaluate("name starts_with ``", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_EntireString_ShouldReturnTrue()
+ {
+ var obj = new { name = "John" };
+ var result = JSONPredicate.Evaluate("name starts_with `John`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_WithArrayElement_ShouldReturnTrue()
+ {
+ var obj = new { tags = new[] { "premium-user", "verified" } };
+ var result = JSONPredicate.Evaluate("tags[0] starts_with `premium`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_StartsWithOperator_WithLogicalOperators_ShouldReturnTrue()
+ {
+ var obj = new { name = "John", status = "active" };
+ var result = JSONPredicate.Evaluate("name starts_with `Jo` and status eq `active`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ // EndsWith Operator Tests - NEW in v1.1.0
+ [Test]
+ public void Evaluate_EndsWithOperator_MatchEnd_ShouldReturnTrue()
+ {
+ var obj = new { email = "john@example.com" };
+ var result = JSONPredicate.Evaluate("email ends_with `.com`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_NoMatch_ShouldReturnFalse()
+ {
+ var obj = new { email = "john@example.com" };
+ var result = JSONPredicate.Evaluate("email ends_with `.org`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_CaseInsensitive_ShouldReturnTrue()
+ {
+ var obj = new { email = "john@EXAMPLE.COM" };
+ var result = JSONPredicate.Evaluate("email ends_with `.com`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_EmptyString_ShouldReturnTrue()
+ {
+ var obj = new { name = "John" };
+ var result = JSONPredicate.Evaluate("name ends_with ``", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_EntireString_ShouldReturnTrue()
+ {
+ var obj = new { name = "John" };
+ var result = JSONPredicate.Evaluate("name ends_with `John`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_WithArrayElement_ShouldReturnTrue()
+ {
+ var obj = new { tags = new[] { "user-premium", "verified" } };
+ var result = JSONPredicate.Evaluate("tags[0] ends_with `premium`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_EndsWithOperator_WithLogicalOperators_ShouldReturnTrue()
+ {
+ var obj = new { name = "Johnson", status = "active" };
+ var result = JSONPredicate.Evaluate("name ends_with `son` and status eq `active`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ // Contains Operator Tests - NEW in v1.1.0
+ [Test]
+ public void Evaluate_ContainsOperator_MatchSubstring_ShouldReturnTrue()
+ {
+ var obj = new { description = "This is a sample text" };
+ var result = JSONPredicate.Evaluate("description contains `sample`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_NoMatch_ShouldReturnFalse()
+ {
+ var obj = new { description = "This is a sample text" };
+ var result = JSONPredicate.Evaluate("description contains `missing`", obj);
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_CaseInsensitive_ShouldReturnTrue()
+ {
+ var obj = new { description = "This Is A Sample Text" };
+ var result = JSONPredicate.Evaluate("description contains `sample`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_MatchEntireString_ShouldReturnTrue()
+ {
+ var obj = new { text = "hello" };
+ var result = JSONPredicate.Evaluate("text contains `hello`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_EmptySubstring_ShouldReturnTrue()
+ {
+ var obj = new { text = "hello" };
+ var result = JSONPredicate.Evaluate("text contains ``", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_WithArrayElement_ShouldReturnTrue()
+ {
+ var obj = new { tags = new[] { "super-premium-package", "verified" } };
+ var result = JSONPredicate.Evaluate("tags[0] contains `premium`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_ContainsOperator_WithLogicalOperators_ShouldReturnTrue()
+ {
+ var obj = new { name = "John Smith", department = "Engineering" };
+ var result = JSONPredicate.Evaluate("name contains `Smith` and department contains `Eng`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ // Combined NEW Features Tests
+ [Test]
+ public void Evaluate_CombinedNewFeatures_ArrayIndexingWithStartsWith_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ tags = new[] { "premium-user", "basic-user", "admin" }
+ };
+ var result = JSONPredicate.Evaluate("tags[0] starts_with `premium`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_CombinedNewFeatures_ArrayIndexingWithEndsWith_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ filenames = new[] { "document.pdf", "image.jpg", "spreadsheet.xlsx" }
+ };
+ var result = JSONPredicate.Evaluate("filenames[1] ends_with `.jpg`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_CombinedNewFeatures_ArrayIndexingWithContains_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ descriptions = new[] { "This is a premium service", "Basic option", "Standard plan" }
+ };
+ var result = JSONPredicate.Evaluate("descriptions[0] contains `premium`", obj);
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void Evaluate_CombinedNewFeatures_ComplexExpressionWithAllNewFeatures_ShouldReturnTrue()
+ {
+ var obj = new
+ {
+ users = new[]
+ {
+ new { name = "John Smith", email = "john.smith@example.com", roles = new[] { "user", "editor" } },
+ new { name = "Jane Doe", email = "jane.doe@work.org", roles = new[] { "admin", "manager" } }
+ }
+ };
+
+ // Complex expression using array indexing, and new string operators
+ var result = JSONPredicate.Evaluate(
+ "users[0].name starts_with `John` and " +
+ "users[1].email ends_with `.org` and " +
+ "users[0].email contains `smith` and " +
+ "users[1].roles[0] eq `admin`", obj);
+
+ Assert.That(result, Is.True);
+ }
}
}
\ No newline at end of file