The FilterRequest JSON shape¶
A FilterRequest is a typed JSON document that carries everything a client needs to express a filter, a sort order, and a page selection. Polymorphism is handled by the bundled FilterNodeJsonConverter — clients never need to declare $type discriminators.
Top-level structure¶
FilterRequest has four fields:
where— aFilterNodetree (group or leaf), ornullto mean "match everything".sort— an array ofSortItem(field+dir), ornullfor the entity's default order.page— 1-based page index.pageSize— items per page, bounded by[PageSettings(MaxPageSize = ...)]when present on the filter class.
{
"where": {
"and": [
{ "field": "Name", "op": "contains", "value": "ali" },
{ "field": "IsActive", "op": "eq", "value": true }
]
},
"sort": [{ "field": "Age", "dir": 1 }],
"page": 1,
"pageSize": 25
}
Nodes — Group vs Leaf¶
FilterNode is the abstract base. Two concrete shapes derive from it:
FilterGroup—{ "and": [...] }or{ "or": [...] }. Carries a list of child nodes and aLogicalOp(And = 0,Or = 1). Groups can nest arbitrarily deep, bounded byMaxNestingDepthin the filter class's[FilterDefaults].FilterLeaf—{ "field": "...", "op": "...", "value": ... }. The value's JSON kind is checked against the operator's expected shape during validation.
A nested example combining both groups:
{
"where": {
"or": [
{ "field": "IsActive", "op": "eq", "value": false },
{
"and": [
{ "field": "Age", "op": "gte", "value": 18 },
{ "field": "Name", "op": "startsWith", "value": "A" }
]
}
]
}
}
This selects users who are inactive or who are at least 18 and whose name starts with A.
Polymorphic deserialization¶
FilterNodeJsonConverter discriminates on the shape of the JSON object:
- Presence of
field(with siblingopandvalue) → deserialize asFilterLeaf. - Presence of
andoror→ deserialize asFilterGroup.
There is no $type discriminator and no JsonPolymorphic attribute. Clients post the natural shape and the converter picks the right runtime type.
For typed leaf values in trim / AOT scenarios, the converter consults a JsonSerializerContext passed via the services.AddFiltering(IJsonTypeInfoResolver) overload. Every value type that appears on the wire — string, int, Guid, custom structs, etc. — must be registered with [JsonSerializable] on that context.
SortItem and SortDir¶
SortItem is a flat record:
field— string name of a property declaredSortable = truein its[Map].dir—SortDirenum:Asc = 0,Desc = 1. Clients post the integer; the converter reads it as the enum.
The same numeric-enum convention applies to LogicalOp (And = 0, Or = 1) — that's the value behind a FilterGroup.Op field, though most clients use the JSON and / or keys directly and never see the enum on the wire.
Tip
Validation rejects sort entries whose field is not Sortable = true with the NotSortable code, and rejects unknown fields anywhere in the tree with UnknownField. See Validation philosophy for the full code list.