Fuuz Bindings: $predicateFilter

Fuuz Bindings: $predicateFilter

Executive Summary

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.

Example:

Query

  1. # query for CalendarEvents...
  2. query GetEvents ($where:CalendarEventWhereInput){
  3.   calendarEvent(where: $where) {
  4. id
  5. timeZone
  6. calendarId
  7.   }
  8. }

Variables

  1. // ...where the CalendarEvent’s timeZone is 'America/Detroit':
  2. {
  3. "where": {
  4. "timeZone": {
  5. "_eq": "America/Detroit"
  6. }
  7. }
  8. }


  1. // ...with Calendar ID "myCalendar" and priority greater than 100:
  2. {
  3. "where": {
  4. "calendarId": {
  5. "_eq": "myCalendar"
  6. },
  7. "priority": {
  8. "_gt": 100
  9. }
  10. }
  11. }





Predicates

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:

    1. 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:

    1. Example:

      $predicateFilter( $data, { "code": { "_eq": "A" }, "active": { "_eq": true } } )


Info
There are subtle differences between these two example snippets! For example, $predicateFilter uses case-insensitive comparison for strings by default.
Predicate Inputs


Universal Predicate Inputs

These predicate inputs are available for use against fields of any type.

Equality Comparison (_eq)

An equality comparison. Matches when the field value equals the provided input value.

Info
String equality is case-insensitive by default (see caseSensitiveComparison)

Payload

  1. [
  2. { "id": 1, "label": "One" },
  3. { "id": 2, "label": "Two" },
  4. { "id": 3, "label": "Three" },
  5. { "id": 4, "label": "Four" }
  6. ]

Transform

  1. $predicateFilter(
  2. $,
  3. { "id": { "_eq": 1 } }
  4. )

Result

  1. [
  2. { "id": 1, "label": "One" }
  3. ]

Is-Null (_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

  1. [
  2. { "id": 1, "userId": "example" },
  3. { "id": 2, "userId": null },
  4. { "id": 3, "userId": "test" },
  5. { "id": 4 }
  6. ]

Transform

  1. $predicateFilter(
  2. $,
  3. { "userId": { "_isNull": true } }
  4. )

Result

  1. [
  2. { "id": 2, "userId": null },
  3. { "id": 4 }
  4. ]

List Membership (_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

  1. [
  2.   {
  3.     "id": "myCalendarEvent1",
  4.     "calendarId": "myCalendar",
  5.     "eventJson": { /* ... */ }
  6.   },
  7.   {
  8.   "id": "otherCalendarEvent1",
  9.     "calendarId": "otherCalendar",
  10.     "eventJson": { /* ... */ }
  11.   },
  12.   {
  13.     "id": "testCalendarEvent",
  14.     "calendarId": "testCalendar",
  15.     "eventJson": { /* ... */ }
  16.   },
  17.   {
  18.     "id": "myCalendarEvent2",
  19.     "calendarId": "myCalendar",
  20.     "eventJson": { /* ... */ }
  21.   },
  22.   {
  23.     "id": "otherCalendarEvent2",
  24.     "calendarId": "otherCalendar",
  25.     "eventJson": { /* ... */ }
  26.   }
  27. ]

Transform

  1. $predicateFilter(
  2.   $,
  3.   { "calendarId": { "_in": ["myCalendar", "testCalendar"] } }
  4. )

 

Result

  1. [
  2.   {
  3. "id": "myCalendarEvent1",
  4. "calendarId": "myCalendar",
  5. "eventJson": {...}
  6. },
  7. {
  8. "id": "testCalendarEvent",
  9. "calendarId": "testCalendar",
  10. "eventJson": {...}
  11. },
  12. {
  13. "id": "myCalendarEvent2",
  14. "calendarId": "myCalendar",
  15. "eventJson": {...}
  16. }
  17. ]

Comparators (_gt/_lt/_gte/_lte)

These predicate inputs compare the field value to the input value using their respective comparisons.

_gt

=

(strictly) greater than

_lt

=

(strictly) less than

_gte

=

greater than or equal

_lte

=

less than or equal



Info
String equality is case-insensitive by default (see caseSensitiveComparison)

Payload

  1. [
  2. { "id": 1, "label": "One" },
  3. { "id": 2, "label": "Two" },
  4. { "id": 3, "label": "Three" },
  5. { "id": 4, "label": "Four" }
  6. ]


Tran
sform
  1. $predicateFilter(
  2. $,
  3. { "id": { "_gt": 2 } }
  4. )

Result

  1. [
  2. { "id": 3, "label": "Three" },
  3. { "id": 4, "label": "Four" }
  4. ]

String-Only Predicate Inputs

These predicate inputs can be used against string fields.

Match Start/End of String (_startsWith/_endsWith)

Performs a starts/ends-with comparison against the field value. Matches if the field value starts/ends with the input value.

Payload

  1. [
  2.   {
  3.     "id": "myCalendarEvent1",
  4.     "calendarId": "myCalendar",
  5.     "eventJson": { /* ... */ }
  6.   },
  7.   {
  8.   "id": "otherCalendarEvent1",
  9.     "calendarId": "otherCalendar",
  10.     "eventJson": { /* ... */ }
  11.   },
  12.   {
  13.     "id": "testCalendarEvent",
  14.     "calendarId": "testCalendar",
  15.     "eventJson": { /* ... */ }
  16.   },
  17.   {
  18.     "id": "myCalendarEvent2",
  19.     "calendarId": "myCalendar",
  20.     "eventJson": { /* ... */ }
  21.   },
  22.   {
  23.     "id": "otherCalendarEvent2",
  24.     "calendarId": "otherCalendar",
  25.     "eventJson": { /* ... */ }
  26.   }
  27. ]

Transform

  1. $predicateFilter(
  2. $,
  3. { "id": { "_endsWith": "Event1" } }
  4. )

Result

  1. [
  2.   {
  3.     "id": "myCalendarEvent1",
  4.     "calendarId": "myCalendar",
  5.     "eventJson": { /* ... */ }
  6.   },
  7.   {
  8.   "id": "otherCalendarEvent1",
  9.     "calendarId": "otherCalendar",
  10.     "eventJson": { /* ... */ }
  11.   }
  12. ]

Contains Substring (_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

  1. [
  2. { "id": "hourly", "label": "Hourly" },
  3. { "id": "daily", "label": "Daily" },
  4. { "id": "weekly", "label": "Weekly" },
  5. { "id": "monthly", "label": "Monthly" },
  6. { "id": "yearly", "label": "Yearly" }
  7. ]

Info

Regular expressions have their own case-sensitivity options, so make sure to use the /i flag if you want case-insensitive regular expressions:

Transform

  1. $predicateFilter(
  2. $,
  3. { "label": { "_contains": "rly" } }
  4. )

Result

  1. [
  2. { "id": "hourly", "label": "Hourly" },
  3. { "id": "yearly", "label": "Yearly" }
  4. ]

Payload

  1. [
  2. { "id": "hourly", "label": "Hourly" },
  3. { "id": "daily", "label": "Daily" },
  4. { "id": "weekly", "label": "Weekly" },
  5. { "id": "monthly", "label": "Monthly" },
  6. { "id": "yearly", "label": "Yearly" }
  7. ]

Transform

  1. $predicateFilter(
  2. $,
  3. { "label": { "_contains": /[aeiou]{2}/i } }
  4. )

Result

  1. [
  2. { "id": "hourly", "label": "Hourly" },
  3. { "id": "daily", "label": "Daily" },
  4. { "id": "weekly", "label": "Weekly" },
  5. { "id": "yearly", "label": "Yearly" }
  6. ]

JSON Predicate Inputs

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:

  1. {
  2. // the object path to test the value against.
  3. // if empty, the value is tested against the whole object.
  4. "path": string[],
  5. // the value to test against the path on the object.
  6. "value": any
  7. }

Contains Object Comparison (_containsObject)

Performs an object contains comparison. The predicate matches when the JSON field contains the provided JSON path/value entries.


Payload
  1. [{ "data": { "id": 1, "code": "A", "active": true } },
  2. { "data": { "id": 2, "code": "B", "active": true } },
  3. { "data": { "id": 3, "code": "A", "active": false } },
  4. { "data": { "id": 4, "code": "B", "active": false } },
  5. { "data": { "id": 5, "code": "A", "active": true} }]

Transform

  1. $predicateFilter(
  2. $,
  3. {
  4. "data": {
  5. "_containsObject": {
  6. "path": [],
  7. "value": { "code": "A", "active": true }
  8. }
  9. }
  10. }
  11. )

Result

  1. [
  2. { "data": { "id": 1, "code": "A", "active": true } },
  3. { "data": { "id": 5, "code": "A", "active": true } }
  4. ]

Info
This function _always_ treats strings case-sensitively. Case-insensitive behavior can be achieved using other predicate inputs.

Object Has Key (_has)

Performs an object-has-key comparison. The predicate matches when the JSON field value contains a key matching the input value.

Payload

  1. [
  2.   { "id": 1, "metadata": { "user": { "id": "example" } } },
  3.   { "id": 2, "metadata": { "user": { "id": null } } },
  4.   { "id": 3, "metadata": { "user": { "id": "test" } } },
  5.   { "id": 4 }
  6. ]

Transform

  1. $predicateFilter(
  2. $,
  3. {
  4.     "metadata": {
  5.       "_has": {
  6.         "path": ["user"],
  7.         "value": "id"
  8.       }
  9.     }
  10.   }
  11. )

Result

  1. [
  2.   { "id": 1, "metadata": { "user": { "id": "example" } } },
  3.   { "id": 2, "metadata": { "user": { "id": null } } },
  4.   { "id": 3, "metadata": { "user": { "id": "test" } } }
  5. ]

Using Universal Predicate Inputs on JSON Objects

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 Style

Predicate 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 Style

The path/value object can be provided to any predicate input to run that predicate against the given path on that field value.

/* 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 JSONPredicateInput type used for filtering JSONObject fields. However, it is more awkward to compose complex object filters in this way, requiring the use of an _and predicate to apply multiple filters to the same object.

// ...Composed in the `JSONPredicateInput` style {   "_and": [     {       "metadata": {         "_in": {           "path": ["user", "id"],           "value": ["example", "test"]         }       }     },     {       "metadata": {         "_lt": {           "path": ["user", "id"],           "value": "foobar"         }       }     }   ] }


Composite Predicate Inputs

_and/_or/_not

Predicates can be logically combined under _and/_or/_not as-follows:

  1. {
  2.   // passes if all predicates in the array pass.
  3.   "_and": [ {/* <predicate 1> */}, {/* <predicate 2> */}, ...],
  4.   // passes if any predicates in the array pass.
  5.   "_or": [ {/* <predicate 1> */}, {/* <predicate 2> */}, ...],
  6.   // passes if the given predicate does not pass.
  7.   "_not": {/* <predicate> */}
  8. }

Using the $predicateFilter Binding

Payloads

Predicate filters can be applied to two kinds of payloads: arrays and objects.

Array Payloads

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

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" } }

Special Cases

Dates

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" } ] */

Keys Conflicting with Predicate Inputs

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 } } */

Options

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" } ] */

$predicateFilter Examples


Equality Comparison

An equality comparison. Matches when the field value equals the provided input value.

$predicateFilter(   [     { "id": 1, "label": "One" },     { "id": 2, "label": "Two" },     { "id": 3, "label": "Three" },     { "id": 4, "label": "Four" }   ],   {     "id": { "_eq": 1 }   } )

Result:

[{ "id": 1, "label": "One" }]

Object Contains Comparison

Performs an object contains comparison. The predicate matches when the JSON field contains the provided JSON path/value entries.

$predicateFilter(   [     { "data": { "id": 1, "code": "A", "active": true } },     { "data": { "id": 2, "code": "B", "active": true } },     { "data": { "id": 3, "code": "A", "active": false } },     { "data": { "id": 4, "code": "B", "active": false } },     { "data": { "id": 5, "code": "A", "active": true } }   ],   {     "data": {       "_containsObject": {         "path": [],         "value": { "code": "A", "active": true }       }     }   } )

Result:

[   { "data": { "id": 1, "code": "A", "active": true } },   { "data": { "id": 5, "code": "A", "active": true } } ]

Object Payload Property Filtering

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.

$predicateFilter(   {     "foo": { "id": 1, "code": "A" },     "bar": { "id": 2, "code": "B" },     "baz": { "id": 3, "code": "a" }   },   { "code": { "_eq": "A" } } )

Result:

{ "foo": { "id": 1, "code": "A" },   "baz": { "id": 3, "code": "a" } }


    • Related Articles

    • Custom Fuuz Only JSONata Library

      These are customized bindings/functions that have been developed for use only within the Fuuz Platform. Type Category Name Signature Description Example Frontend Clipboard $writeToClipboard() $writeToClipboard(data) Write data to clipboard ...
    • Getting Started With Data Flow Nodes

      This article provides the information and resources to support the task of working with data flow nodes. The data flow system supports a wide variety of nodes, with more being added regularly. We always welcome feedback or ideas on new nodes that ...
    • Getting Started with Fuuz Scripting

      This article provides an introduction to Fuuz scripting and its relevant training resources. Fuuz Scripting Language Fuuz Scripting Windows Fuuz Scripting Language Fuuz's scripting language is an extension of the JSONata transformation language. Fuuz ...
    • Create a Document With Fuuz Data

      This article provides information resources, prerequisites, and instructions for creating documents with MFGx. Prerequisites Pulling Data Report Transform Evaluated Transform Prerequisites Become familiar with the Fuuz API. Become familiar with the ...
    • Fuuz Browser Extension Installation

      This article provides the steps and resources to support the task of installing and using the MFGx/Fuuz Browser Extension. Installation Group Policy / Remote Installation General Usage Log In Select A Tenant Access The Extension Features Add And Edit ...