Every model in the Fuuz GraphQL API has a generic WhereInput type used to filter queries against that model. These WhereInput types assign a PredicateInput type to each field on the model. If you have made a query against a Fuuz GraphQL API you have likely used a WhereInput to filter the results. These predicates provide a quick and easy way to define filters through a Fuuz GraphQL API, making them a natural fit for the internal workings of screens and Data Flows.
Query
| Variables
|
|
A predicate can be thought of as a “test” that data must pass in order to be accepted by the filter: if you’re using this binding, then you should be familiar with this concept from Jsonata’s $filter
function. The main difference between $filter
and $predicateFilter
is one of representation:
$filter
A predicate is represented as a function.
An element is accepted if the predicate function returns true
when applied to it.
Even relatively simple predicates need to be expressed in terms of functions:
Example:
$filter( $data, function($element) { $element.code = "A" and $element.active = true } )
$predicateFilter
A predicate is expressed as an object of predicate inputs. (Described later.)
An element is accepted if it satisfies all of the inputs.
Predicates look more like the data they are being used to filter:
Example:
$predicateFilter( $data, { "code": { "_eq": "A" }, "active": { "_eq": true } } )
These predicate inputs are available for use against fields of any type.
_eq
)An equality comparison. Matches when the field value equals the provided input value.
Payload
- [
- { "id": 1, "label": "One" },
- { "id": 2, "label": "Two" },
- { "id": 3, "label": "Three" },
- { "id": 4, "label": "Four" }
- ]
Transform
- $predicateFilter(
- $,
- { "id": { "_eq": 1 } }
- )
Result
- [
- { "id": 1, "label": "One" }
- ]
_isNull
)A null-checking comparison.
An input value of true
matches where the field value is null
or undefined.
Any other input value matches where the field value is not null.
Payload
- [
- { "id": 1, "userId": "example" },
- { "id": 2, "userId": null },
- { "id": 3, "userId": "test" },
- { "id": 4 }
- ]
Transform
- $predicateFilter(
- $,
- { "userId": { "_isNull": true } }
- )
Result
- [
- { "id": 2, "userId": null },
- { "id": 4 }
- ]
_in
)Matches when the field value is contained in the list provided by the input value. (Membership is tested using the same rules as _eq
.)
Payload
- [
- {
- "id": "myCalendarEvent1",
- "calendarId": "myCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "otherCalendarEvent1",
- "calendarId": "otherCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "testCalendarEvent",
- "calendarId": "testCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "myCalendarEvent2",
- "calendarId": "myCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "otherCalendarEvent2",
- "calendarId": "otherCalendar",
- "eventJson": { /* ... */ }
- }
- ]
Transform
- $predicateFilter(
- $,
- { "calendarId": { "_in": ["myCalendar", "testCalendar"] } }
- )
Result
- [
- {
- "id": "myCalendarEvent1",
- "calendarId": "myCalendar",
- "eventJson": {...}
- },
- {
- "id": "testCalendarEvent",
- "calendarId": "testCalendar",
- "eventJson": {...}
- },
- {
- "id": "myCalendarEvent2",
- "calendarId": "myCalendar",
- "eventJson": {...}
- }
- ]
_gt
/_lt
/_gte
/_lte
)These predicate inputs compare the field value to the input value using their respective comparisons.
| = | (strictly) greater than |
| = | (strictly) less than |
| = | greater than or equal |
| = | less than or equal |
Payload
- [
- { "id": 1, "label": "One" },
- { "id": 2, "label": "Two" },
- { "id": 3, "label": "Three" },
- { "id": 4, "label": "Four" }
- ]
Result
- [
- { "id": 3, "label": "Three" },
- { "id": 4, "label": "Four" }
- ]
These predicate inputs can be used against string fields.
_startsWith
/_endsWith
)Performs a starts/ends-with comparison against the field value. Matches if the field value starts/ends with the input value.
Payload
- [
- {
- "id": "myCalendarEvent1",
- "calendarId": "myCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "otherCalendarEvent1",
- "calendarId": "otherCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "testCalendarEvent",
- "calendarId": "testCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "myCalendarEvent2",
- "calendarId": "myCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "otherCalendarEvent2",
- "calendarId": "otherCalendar",
- "eventJson": { /* ... */ }
- }
- ]
Transform
- $predicateFilter(
- $,
- { "id": { "_endsWith": "Event1" } }
- )
Result
- [
- {
- "id": "myCalendarEvent1",
- "calendarId": "myCalendar",
- "eventJson": { /* ... */ }
- },
- {
- "id": "otherCalendarEvent1",
- "calendarId": "otherCalendar",
- "eventJson": { /* ... */ }
- }
- ]
_contains
)Performs a contains comparison against the field value. Matches if the field value contains the input value. The input value can be either a string or a regular expression.
Payload
- [
- { "id": "hourly", "label": "Hourly" },
- { "id": "daily", "label": "Daily" },
- { "id": "weekly", "label": "Weekly" },
- { "id": "monthly", "label": "Monthly" },
- { "id": "yearly", "label": "Yearly" }
- ]
Regular expressions have their own case-sensitivity options, so make sure to use the /i flag if you want case-insensitive regular expressions:
Transform
- $predicateFilter(
- $,
- { "label": { "_contains": "rly" } }
- )
Result
- [
- { "id": "hourly", "label": "Hourly" },
- { "id": "yearly", "label": "Yearly" }
- ]
Payload
- [
- { "id": "hourly", "label": "Hourly" },
- { "id": "daily", "label": "Daily" },
- { "id": "weekly", "label": "Weekly" },
- { "id": "monthly", "label": "Monthly" },
- { "id": "yearly", "label": "Yearly" }
- ]
Transform
- $predicateFilter(
- $,
- { "label": { "_contains": /[aeiou]{2}/i } }
- )
Result
- [
- { "id": "hourly", "label": "Hourly" },
- { "id": "daily", "label": "Daily" },
- { "id": "weekly", "label": "Weekly" },
- { "id": "yearly", "label": "Yearly" }
- ]
These predicate inputs can be used on JSON object fields. Unlike the previous examples, JSON predicate input values are provided as objects with two properties:
- {
- // the object path to test the value against.
- // if empty, the value is tested against the whole object.
- "path": string[],
- // the value to test against the path on the object.
- "value": any
- }
_containsObject
)Performs an object contains comparison. The predicate matches when the JSON field contains the provided JSON path/value entries.
Transform
- $predicateFilter(
- $,
- {
- "data": {
- "_containsObject": {
- "path": [],
- "value": { "code": "A", "active": true }
- }
- }
- }
- )
Result
- [
- { "data": { "id": 1, "code": "A", "active": true } },
- { "data": { "id": 5, "code": "A", "active": true } }
- ]
_has
)Performs an object-has-key comparison. The predicate matches when the JSON field value contains a key matching the input value.
Payload
- [
- { "id": 1, "metadata": { "user": { "id": "example" } } },
- { "id": 2, "metadata": { "user": { "id": null } } },
- { "id": 3, "metadata": { "user": { "id": "test" } } },
- { "id": 4 }
- ]
Transform
- $predicateFilter(
- $,
- {
- "metadata": {
- "_has": {
- "path": ["user"],
- "value": "id"
- }
- }
- }
- )
Result
- [
- { "id": 1, "metadata": { "user": { "id": "example" } } },
- { "id": 2, "metadata": { "user": { "id": null } } },
- { "id": 3, "metadata": { "user": { "id": "test" } } }
- ]
All prior predicate inputs (_eq
/_isNull
/_in
/etc.) can be used on JSON objects as well. These input values can be provided in two ways:
Nested StylePredicate inputs can be nested under any number of property names, implicitly defining the path and value to test the predicate against: /* Nested Style (implicit `path`/`value`) */
$predicateFilter(
[
{ "id": 1, "metadata": { "user": { "id": "example" } } },
{ "id": 2, "metadata": { "user": { "id": null } } },
{ "id": 3, "metadata": { "user": { "id": "test" } } },
{ "id": 4 }
],
{
"metadata": {
"user": {
"id": { "_in": ["example", "test"] }
}
}
}
) /* -> [
{ "id": 1, "metadata": { "user": { "id": "example" } } },
{ "id": 3, "metadata": { "user": { "id": "test" } } }
] */ This approach is intuitive, but can lead to some issues when property names conflict with predicates (see Special Cases ). It also doesn't translate as neatly to query filters, since this structure doesn't match the JSONPredicateInput type used for JSONObject query fields.// This nested-style predicate...
{
"metadata": {
"user": {
"id": {
"_in": ["example", "test"],
"_lt": "foobar"
}
}
}
} | JSON Predicate Input StyleThe /* JSON Predicate Input Style (explicit `path`/`value`) */
$predicateFilter(
[
{ "id": 1, "metadata": { "user": { "id": "example" } } },
{ "id": 2, "metadata": { "user": { "id": null } } },
{ "id": 3, "metadata": { "user": { "id": "test" } } },
{ "id": 4 }
],
{
"metadata": {
"_in": {
"path": ["user", "id"],
"value": ["example", "test"]
}
}
}
) /* -> [
{ "id": 1, "metadata": { "user": { "id": "example" } } },
{ "id": 3, "metadata": { "user": { "id": "test" } } }
] */ This approach is more explicit and better-reflects the // ...Composed in the `JSONPredicateInput` style
{
"_and": [
{
"metadata": {
"_in": {
"path": ["user", "id"],
"value": ["example", "test"]
}
}
},
{
"metadata": {
"_lt": {
"path": ["user", "id"],
"value": "foobar"
}
}
}
]
} |
_and
/_or
/_not
Predicates can be logically combined under _and
/_or
/_not
as-follows:
- {
- // passes if all predicates in the array pass.
- "_and": [ {/* <predicate 1> */}, {/* <predicate 2> */}, ...],
- // passes if any predicates in the array pass.
- "_or": [ {/* <predicate 1> */}, {/* <predicate 2> */}, ...],
- // passes if the given predicate does not pass.
- "_not": {/* <predicate> */}
- }
$predicateFilter
BindingPredicate filters can be applied to two kinds of payloads: arrays and objects.
Array payloads are filtered in a similar way as with the $filter
function. Each element of the array is tested individually and included in the result only if it satisfies the provided predicate.
Payload
[ { "id": 1, "code": "A" }, { "id": 2, "code": "B" }, { "id": 3, "code": "a" } ]
Transform
$predicateFilter( $, { "code": { "_eq": "A" } } )
Result
[ { "id": 1, "code": "A" }, { "id": 3, "code": "a" } ]
Object payloads are filtered in a similar way as with the `$sift` function. Each property of the object is included in the result only if its value satisfies the provided predicate.
Payload
{ "foo": { "id": 1, "code": "A" }, "bar": { "id": 2, "code": "B" }, "baz": { "id": 3, "code": "a" } }
Transform
$predicateFilter( $, { "code": { "_eq": "A" } } )
Result
{ "foo": { "id": 1, "code": "A" }, "baz": { "id": 3, "code": "a" } }
Dates in JSON data are typically represented as formatted strings. This is also true for query filters against DateTime
fields, since the GraphQL API knows which fields are supposed to be dates and which fields are just plain strings.
This is not true for the $predicateFilter
binding; it does not know when a given string is supposed to be a date or not, and therefore will not compare formatted strings correctly as dates.
For example:
/* THIS WILL NOT FILTER DATES PROPERLY */ $predicateFilter( [ { "id": 0, "code": "CODE-A", "time": "2024-01-01T00:00:00.000-04:00" }, { "id": 1, "code": "CODE-B", "time": "2024-01-01T00:00:00.000-02:00" }, { "id": 2, "code": "CODE-C", "time": "2024-01-01T00:00:00.000+00:00" }, { "id": 3, "code": "CODE-D", "time": "2024-01-01T00:00:00.000+02:00" }, { "id": 4, "code": "CODE-E", "time": "2024-01-01T00:00:00.000+04:00" } ], { "time": { "_gte": "2024-01-01T00:00:00.000Z" } } ) /* -> [] (INCORRECT) */
When working with dates, ensure that both your payload and predicate fields are cast to Date
objects or moment
instances if you want to compare them.
$predicateFilter( [ { "id": 0, "code": "CODE-A", "time": $moment("2024-01-01T00:00:00.000-04:00") }, { "id": 1, "code": "CODE-B", "time": $moment("2024-01-01T00:00:00.000-02:00") }, { "id": 2, "code": "CODE-C", "time": $moment("2024-01-01T00:00:00.000+00:00") }, { "id": 3, "code": "CODE-D", "time": $moment("2024-01-01T00:00:00.000+02:00") }, { "id": 4, "code": "CODE-E", "time": $moment("2024-01-01T00:00:00.000+04:00") } ], { "time": { "_gte": $moment("2024-01-01T00:00:00.000Z") } } ) /* -> [ { "id": 0, "code": "CODE-A", "time": "2024-01-01T04:00:00.000Z" }, { "id": 1, "code": "CODE-B", "time": "2024-01-01T02:00:00.000Z" }, { "id": 2, "code": "CODE-C", "time": "2024-01-01T00:00:00.000Z" } ] */
Sometimes, payload elements will contain properties with names matching predicate input names (like _eq
or _in
).
For an (admittedly-contrived) example, you might want to remove filters from a WhereInput
object when they contain "_isNull": true
:
/* THIS WILL NOT FILTER PROPERLY */ $predicateFilter( /* A hypothetical WhereInput to be used in a query */ { "name": { "_contains": "foo" }, "metadata": { "_isNull": false }, "userId": { "_in": ["example", "test"], "_isNull": true } }, /* trying to filter out fields containing `{ "_isNull": true }` */ { "_not": { "_isNull": { "_eq": true } } } )
The property name "_isNull
" conflicts with the _isNull
predicate input, making the filter ambiguous. In these ambiguous cases, the $predicateFilter
binding always prefers to interpret it as a predicate instead of an implicit path. To disambiguate, use the explicit path
/value
style described in the JSON Predicate Input Style section.
$predicateFilter( { "name": { "_contains": "foo" }, "metadata": { "_isNull": false }, "userId": { "_in": ["example", "test"], "_isNull": true } }, { "_not": { /* the `path`/`value` style for JSON properties is unambiguous */ "_eq": { "path": ["_isNull"], "value": true } } } ) /* -> { "name": { "_contains": "foo" }, "metadata": { "_isNull": false } } */
caseSensitiveComparison
(Boolean: defaults to false
)
While false
(the default) string values will be compared case-insensitively. (Both predicate and payload field values will be converted to lowercase for comparison.) This reflects the behavior of a Fuuz GraphQL API, which matches string fields case-insensitively.
While true
, string fields will be kept in their original casing for all comparisons.
$predicateFilter( [ { "id": 1, "code": "A" }, { "id": 2, "code": "B" }, { "id": 3, "code": "a" } ], { "code": { "_gte": "B" } } ) /* -> [{ "id": 2, "code": "B" }] */ $predicateFilter( [ { "id": 1, "code": "A" }, { "id": 2, "code": "B" }, { "id": 3, "code": "a" } ], { "code": { "_gte": "B" } }, { "caseSensitiveComparison": true } ) /* -> [ { "id": 2, "code": "B" }, { "id": 3, "code": "a" } ] */