![]() |
VOOZH | about |
dotnet add package DataFilters --version 0.13.2
NuGet\Install-Package DataFilters -Version 0.13.2
<PackageReference Include="DataFilters" Version="0.13.2" />
<PackageVersion Include="DataFilters" Version="0.13.2" />Directory.Packages.props
<PackageReference Include="DataFilters" />Project file
paket add DataFilters --version 0.13.2
#r "nuget: DataFilters, 0.13.2"
#:package DataFilters@0.13.2
#addin nuget:?package=DataFilters&version=0.13.2Install as a Cake Addin
#tool nuget:?package=DataFilters&version=0.13.2Install as a Cake Tool
👁 GitHub Main branch status
👁 GitHub Develop branch status
👁 Unit tests code coverage
👁 GitHub raw issues
👁 DataFilters latest nuget package version (including beta)
A small library that allow to convert a string to a generic object. Highly inspired by the elastic query syntax, it offers a powerful way to build and query data with a syntax that's not bound to a peculiar datasource.
This project adheres to Semantic Versioning.
Major version zero (0.y.z) is for initial development. Anything MAY change at any time.
The public API SHOULD NOT be considered stable.
The idea came to me when working on a set of REST APIs and trying to build /search endpoints.
I wanted to have a uniform way to query a collection of resources whilst abstracting away underlying datasources.
Let's say your API handles vigilante resources :
public class Vigilante
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Nickname {get; set; }
public int Age { get; set; }
public string Description {get; set;}
public IEnumerable<string> Powers {get; set;}
public IEnumerable<Vigilante> Acolytes {get; set;}
}
JSON Schema
{
"id": "vigilante_root",
"title": "Vigilante",
"type": "object",
"properties": {
"firstname": {
"required": true,
"type": "string"
},
"lastname": {
"required": true,
"type": "string"
},
"nickname": {
"required": true,
"type": "string"
},
"age": {
"required": true,
"type": "integer"
},
"description": {
"required": true,
"type": "string"
},
"powers": {
"required": true,
"type": "array",
"items": {
"type": "string"
}
},
"acolytes": {
"required": true,
"type": "array",
"items": {
"$ref": "vigilante_root"
}
}
}
}
and the base URL of your API is https://my-beautiful/api.
vigilante resources could then be located at https://my-beautiful/api/vigilantes/
Wouldn't it be nice to be able to search any resource like so
https://my-beautiful/api/vigilantes/search?nickname=Bat*|Super* ?
This is exactly what this project is about : giving you an uniform syntax to query resources without having to think about the underlying datasource.
This is the first step on filtering data. Thanks to SuperPower,
the library supports a custom syntax that can be used to specified one or more criteria resources must fullfill.
The currently supported syntax mimic the query string syntax : a key-value pair separated by ampersand (& character) where :
field is the name of a property of the resource to filtervalue is an expression which syntax is highly inspired by the Lucene syntaxTo parse an expression, simply call ToFilter<T> extension method
(see unit tests for more details on the syntax)
Several expressions are supported and here's how you can start using them in your search queries.
string |
numeric types (int, short, ...) |
Date and time types (DateTime, DateTimeOffset, ...) |
|
|---|---|---|---|
| EqualTo | ✅ | ✅ | ✅ |
| StartsWith | ✅ | N/A | N/A |
| Ends with | ✅ | N/A | N/A |
| Contains | ✅ | N/A | N/A |
| IsNull | ✅ | N/A | N/A |
| IsNotNull | ✅ | N/A | N/A |
| LessThanOrEqualTo | N/A | ✅ | ✅ |
| GreaterThanOrEqualTo | N/A | ✅ | ✅ |
| bracket expression | N/A | ✅ | ✅ |
Search for any vigilante resources where the value of the property nickname is manbat
| Query string | JSON | C# |
|---|---|---|
nickname=manbat |
{ "field":"nickname", "op":"eq", "value":"manbat" } |
new Filter(field: "nickname", @operator : FilterOperator.EqualsTo, value : "manbat") |
Search for any vigilante resources where the value of the property nickname starts with "bat"
| Query string | JSON | C# |
|---|---|---|
nickname=bat* |
{ "field":"nickname", "op":"startswith", "value":"bat" } |
new Filter(field: "nickname", @operator : FilterOperator.StartsWith, value : "bat") |
Search for vigilante resources where the value of the property nickname ends with man.
| Query string | JSON | C# |
|---|---|---|
nickname=*man |
{ "field":"nickname", "op":"endswith", "value":"man" } |
new Filter(field: "nickname", @operator : FilterOperator.EndsWith, value : "man") |
Search for any vigilante resources where the value of the property nickname contains "bat".
| Query string | JSON | C# |
|---|---|---|
nickname=*bat* |
{ "field":"nickname", "op":"contains", "value":"bat" } |
new Filter(field: "nickname", @operator : FilterOperator.Contains, value : "bat") |
💡 contains also work on arrays. powers=*strength* will search for vigilantes who have strength related powers.
Search for vigilante resources that have no powers.
| Query string | JSON | C# |
|---|---|---|
powers=!* |
{ "field":"powers", "op":"isempty" } |
new Filter(field: "powers", @operator : FilterOperator.IsEmpty) |
Search for vigilante resources that have no powers.
| Query string | JSON | C# |
|---|---|---|
N/A |
{ "field":"powers", "op":"isnull" } |
new Filter(field: "powers", @operator : FilterOperator.IsNull) or new Filter(field: "powers", @operator : FilterOperator.EqualsTo, value: null) |
Search for vigilante resources that have at least one of the specified powers.
| Query string | JSON |
|---|---|
powers={strength\|speed\|size} |
N/A |
will result in a instance equivalent to
IFilter filter = new MultiFilter
{
Logic = Or,
Filters = new IFilter[]
{
new Filter("powers", EqualTo, "strength"),
new Filter("powers", EqualTo, "speed"),
new Filter("powers", EqualTo, "size")
}
};
Search for vigilante resources that have no powers.
| Query string | JSON | C# |
|---|---|---|
N/A |
{ "field":"powers", "op":"isnotnull" } |
(new Filter(field: "powers", @operator : FilterOperator.IsNull)).Negate() or new Filter(field: "powers", @operator : FilterOperator.NotEqualTo, value: null) |
Interval expressions are delimited by upper and a lower bound. The generic syntax is
<field>=<min> TO <max>
where
field is the name of the property current interval expression will be apply tomin is the lowest bound of the intervalmax is the highest bound of the intervalSearch for vigilante resources where the value of age property is greater than or equal to 18
| Query string | JSON | C# |
|---|---|---|
age=[18 TO *[ |
{"field":"age", "op":"gte", "value":18} |
new Filter(field: "age", @operator : FilterOperator.GreaterThanOrEqualTo, value : 18) |
Search for vigilante resource where the value of age property is lower than 30
| Query string | JSON | C# |
|---|---|---|
age=]* TO 30] |
{"field":"age", "op":"lte", "value":30} |
new Filter(field: "age", @operator : FilterOperator.LessThanOrEqualTo, value : 30) |
Search for vigilante resources where age property is between 20 and 35
| Query string | JSON | C# |
|---|---|---|
age=[20 TO 35] |
{"logic": "and", filters[{"field":"age", "op":"gte", "value":20}, {"field":"age", "op":"lte", "value":35}]} |
new MultiFilter { Logic = And, Filters = new IFilter[] { new Filter ("age", GreaterThanOrEqualTo, 20), new Filter("age", LessThanOrEqualTo, 35) } } |
💡 You can exclude the lower (resp. upper) bound by using ] (resp. [).
age=]20 TO 35[ means age strictly greater than 20 and strictly less than35age=[20 TO 35[ means age greater than or equal to 20 and strictly less than35age=]20 TO 35] means age greater than 20 and less than or equal to 35💡 Dates, times and durations must be specified in ISO 8601 format
Examples :
]1998-10-26 TO 2000-12-10[my/beautiful/api/search?date=]1998-10-26 10:00 TO 1998-10-26 10:00[]1998-10-12T12:20:00 TO 13:30[ is equivalent to ]1998-10-12T12:20:00 TO 1998-10-12T13:30:00[💡 You can apply filters to any sub-property of a given collection
Example :
acolytes["name"]='robin' will filter any vigilante resource where at least one item in acolytes array with name equals to robin.
The generic syntax for filtering on in a hierarchical tree
property["subproperty"]...["subproperty-n"]=<expression>
you can also use the dot character (.).
property["subproperty"]["subproperty-n"]=<expression> and property.subproperty["subproperty-n"]=<expression>
are equivalent
The library offers a limited support of regular expressions. To be more specific, only bracket expressions are currently supported. A bracket expression. Matches a single character that is contained within the brackets.
For example:
[abc] matches a, b, or c[a-z] specifies a range which matches any lowercase letter from a to z.s can be, as any other expressions, combined with any other expressions to build more complex expressions.
Logicial operators can be used combine several instances of together.
Use the coma character , to combine multiple expressions using logical AND operator
| Query string | JSON |
|---|---|
nickname=Bat*,*man |
{"logic": "and", filters[{"field":"nickname", "op":"startswith", "value":"Bat"}, {"field":"nckname", "op":"endswith", "value":"man"}]} |
will result in a instance equivalent to
IFilter filter = new MultiFilter
{
Logic = And,
Filters = new IFilter[]
{
new Filter("nickname", StartsWith, "Bat"),
new Filter("nickname", EndsWith, "man")
}
}
Use the pipe character | to combine several expressions using logical OR operator
Search for vigilante resources where the value of the nickname property either starts with "Bat" or
ends with "man"
| Query string | JSON |
|---|---|
nickname=Bat*\|*man |
{"logic": "or", filters[{"field":"nickname", "op":"startswith", "value":"Bat"}, {"field":"nckname", "op":"endswith", "value":"man"}]} |
will result in
IFilter filter = new MultiFilter
{
Logic = Or,
Filters = new IFilter[]
{
new Filter("nickname", StartsWith, "Bat"),
new Filter("nickname", EndsWith, "man")
}
}
To negate a filter, simply put a ! before the expression to negate
Search for vigilante resources where the value of nickname property does not starts with "B"
| Query string | JSON |
|---|---|
nickname=!B* |
{"field":"nickname", "op":"nstartswith", "value":"B"} |
will be parsed into a instance equivalent to
IFilter filter = new Filter("nickname", DoesNotStartWith, "B");
Expressions can be arbitrarily complex.
"nickname=(Bat*|Sup*)|(*man|*er)"
Explanation :
The criteria under construction will be applied to the value of nickname property and can be read as follow :
Searchs for vigilante resources that starts with Bat or Sup and ends with man or
er.
will be parsed into a
IFilter filter = new MultiFilter
{
Logic = Or,
Filters = new IFilter[]
{
new MultiFilter
{
Logic = Or,
Filters = new IFilter[]
{
new Filter("Firstname", StartsWith, "Bat"),
new Filter("Firstname", StartsWith, "Sup"),
}
},
new MultiFilter
{
Logic = Or,
Filters = new IFilter[]
{
new Filter("Firstname", EndsWith, "man"),
new Filter("Firstname", EndsWith, "er"),
}
},
}
}
The ( and ) characters allows to group two expressions together so that this group can be used as a more complex
expression unit.
Sometimes, you'll be looking for a filter that match exactly a text that contains a character which has a special meaning.
The backslash character (\) can be used to escape characters that will be otherwise interpreted as
a special character.
| Query string | JSON | C# |
|---|---|---|
comment=*\! |
{"field":"comment", "op":"endswith", "value":"!"} |
new Filter(field: "comments", @operator: FilterOperator.EndsWith, value: "!") |
💡 Escaping special characters can be a tedious task when working with longer texts. Just use a text expression instead by wrapping
the text between double quotes (").
| Query string | JSON | C# |
|---|---|---|
comment=*"!" |
{"field":"comment", "op":"endswith", "value":"!"} |
new Filter(field: "comments", @operator: FilterOperator.EndsWith, value: "!") |
Example :
I'm a long text with some \"special characters\" in it and each one must be escaped properly`
can be rewritten
"I'm a long text with some \"special characters\" in it and each one must be escaped properly !`
When using text expressions, only \ and " characters need to be escaped.
This library also supports a custom syntax to sort elements.
sort=nickname or sort=+nickname sort items by their nickname properties in ascending
order.
You can sort by several properties at once by separating them with a ,.
For example sort=+nickname,-age allows to sort by nickname ascending, then by age property descending.
dotnet install DataFilters : you can already start building instances 😉 !DataFilters.XXXX extension packages to convert instances to various target.So you have your API and want provide a great search experience ?
The client will have the responsability of building search criteria. Go to filtering and sorting sections to see example on how to get started.
One way to start could be by having a dedicated resource which properties match the resource's properties search will be performed onto.
Continuing with our vigilante API, we could have
// Wraps the search criteria for Vigilante resources.
public class SearchVigilanteQuery
{
public string Firstname {get; set;}
public string Lastname {get; set;}
public string Nickname {get; set;}
public int? Age {get; set;}
}
and the following endpoint
using DataFilters;
public class VigilantesController
{
// code omitted for brievity
[HttpGet("search")]
[HttpHead("search")]
public ActionResult Search([FromQuery] SearchVigilanteQuery query)
{
IList<IFilter> filters = new List<IFilter>();
if(!string.IsNullOrWhitespace(query.Firstname))
{
filters.Add($"{nameof(Vigilante.Firstname)}={query.Firstname}".ToFilter<Vigilante>());
}
if(!string.IsNullOrWhitespace(query.Lastname))
{
filters.Add($"{nameof(Vigilante.Lastname)}={query.Lastname}".ToFilter<Vigilante>());
}
if(!string.IsNullOrWhitespace(query.Nickname))
{
filters.Add($"{nameof(Vigilante.Nickname)}={query.Nickname}".ToFilter<Vigilante>());
}
if(query.Age.HasValue)
{
filters.Add($"{nameof(Vigilante.Age)}={query.Age.Value}".ToFilter<Vigilante>());
}
IFilter filter = filters.Count() == 1
? filters.Single()
: new MultiFilter{ Logic = And, Filters = filters };
// filter now contains our search criteria and is ready to be used 😊
}
}
Some explanation on the controller's code above :
GET and HEAD requests on /vigilante/searchquery parameter accordingly..ToFilter<T>() string extension method is available. It turns a query-string key-value pair into a
full .💡 Remarks
You may have noticed that SearchVigilanteQuery.Age property is nullable whereas Vigilante.Age property is not.
This is to distinguish if the Age criterion was provided or not when calling the vigilantes/search endpoint.
Most of the time, once you have an , you want to use it against a datasource.
Using Expression<Func<T, bool>> is the most common type used for this kind of purpose.
DataFilters.Expressions library adds ToExpression<T>() extension method on top of instance to convert it
to an equivalent System.Expression<Func<T, bool>> instance.
Using the example of the VigilantesController, we can turn our filter into a Expression<Func<T, bool>>
IFilter filter = ...
Expression<Func<Vigilante, bool>> predicate = filter.ToExpression<Vigilante>();
The predicate expression can now be used against any datasource that accepts Expression<Func<Vigilante, bool>> (👋🏾 EntityFramework and the likes )
IFIltersWhat to do when you cannot use expression trees when querying your datasource ? Well, you can write your own method to render it duh !!!
DataFilters.Queries👁 Nuget
adds ToWhere<T>() extension
method on top of instance to convert
it to an equivalent IWhereClause instance.
IWhereClause is an interface from the Queries that
can later be translated a secure SQL string.
You can find more info on that directly in the Github repository.
| Package | Downloads | Description |
|---|---|---|
| 👁 Nuget |
👁 DataFilters download count |
provides core functionalities of parsing strings and converting to instances. |
| 👁 Nuget |
👁 DataFilters.Expressions download count |
adds ToExpression<T>() extension method on top of instance to convert it to an equivalent System.Linq.Expressions.Expression<Func<T, bool>> instance. |
| 👁 Nuget |
👁 DataFilters.Queries download count |
adds ToWhere<T>() extension method on top of instance to convert it to an equivalent IWhereClause instance. |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 netstandard2.1 is compatible. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 3 NuGet packages that depend on DataFilters:
| Package | Downloads |
|---|---|
|
DataFilters.Expressions
Converts IFilter instance to strongly typed expressions. |
|
|
DataFilters.Queries
Provides extension methods to convert IFilter to IWhereClause and IOrder to ISort. |
|
|
DataFilters.AspNetCore
Package Description |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.13.2 | 5,698 | 9/14/2025 |
| 0.13.2-fix.2 | 109 | 9/12/2025 |
| 0.13.1 | 577 | 1/12/2025 |
| 0.13.1-fix0001 | 300 | 9/7/2024 |
| 0.13.1-fix.404 | 129 | 1/12/2025 |
| 0.13.1-fix.402 | 125 | 1/12/2025 |
| 0.13.1-fix.401 | 140 | 1/12/2025 |
| 0.13.1-fix.400 | 127 | 1/12/2025 |
| 0.13.1-fix.31 | 171 | 9/14/2024 |
| 0.13.1-fix.30 | 135 | 9/14/2024 |
| 0.13.1-fix.29 | 135 | 9/14/2024 |
| 0.13.1-beta0023 | 333 | 9/8/2024 |
| 0.13.1-beta0001 | 322 | 7/12/2024 |
| 0.13.0 | 1,047 | 7/11/2024 |
| 0.13.0-beta0001 | 325 | 7/7/2024 |
| 0.12.0 | 17,600 | 10/12/2022 |
| 0.12.0-beta0001 | 486 | 4/27/2022 |
| 0.11.0 | 13,129 | 3/13/2022 |
| 0.10.2 | 1,245 | 3/9/2022 |
| 0.10.0 | 1,276 | 1/12/2022 |
### 🚨 Fixes
• Fixed incorrect handling of nested properties when using sub-property syntax ([#477](https://github.com/candoumbe/datafilters/issues/477))
Full changelog at https://github.com/candoumbe/DataFilters/blob/main/CHANGELOG.md