Introduction
I wanted to share/propose the filtering strategy I designed when it came time for adding filtering of resource collections. After much debate, the filtering strategy I decided upon was to adopt a “subset” or “best of” from the OData 4.0 query standard for the following reasons:
- Well designed and maintained query options standard, why reinvent the wheel.
- Same reason why I adopted the json-api standard, standards are good!
- Already an adopted industry standard and well documented.
- After much review I found it expressive, URL friendly, and would meet almost 100% of my needs for filtering.
Here is the OData 4.0 documentation for it’s URL conventions: http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html
Again I am not proposing the entire OData query syntax, only the best parts with minor tweaking if necessary. With that said here is what I have currently adopted and have working for filtering of our json:api based resource collections:
Logical Operators
-
eq
operator (Equals) -
ne
operator (Not Equals) -
gt
operator (Greater Than) -
ge
operator (Greater Than or Equal) -
lt
operator (Less Than) -
le
operator (Less Than or Equal) -
and
operator (And) -
or
operator (Or) -
not
operator (Not)
Grouping
Grouping with the open (
and close )
parenthesis operators.
String Functions
-
contains
function (filters if a string contains another substring) -
startswith
function (filters if string startswith a substring) -
endswith
function (filters if string endswith a substring)
Geography Functions
-
geo.distance
function (filters on distance between 2 geographical points (latitude/longitude))
In Operator
The OData standard does not currently support the in
operator but has an open requirement to add it. It was so fundamental that I added the in
operator based on the open pull request that will be adding it to the OData standard.
Filter Examples:
The following are examples of filtering based on this OData “subset” and “best-of” that I have currently implemented and have working:
Assume we have a Movie
resource collection at: /movies
/movies?filter=movie-name eq 'Braveheart'
/movies?filter=movie-rating eq 'G' or movie-rating eq 'PG-13'
/movies?filter=contains(movie-name, 'Star Wars') and price lt 10
/movies?filter=runtime-in-minutes ge 90 and runtime-in-minutes le 120 and rating in ('R', 'PG-13')
/movies?filter=schedule-date eq '12/25/2015' and geo.distance(theater-location, POINT(-80.2008 26.3681)) le 10000
/movies?filter=not(startswith(movie-name, 'The') and movie-rating in ('G', 'PG-13')) or schedule-date eq '1/1/2016'
Conclusion
As you can see with just the “subset” and “best-of” from the OData query standard we have a powerful filter framework to filter our resource collections currently. It was an interesting engineering challenge to create the lexical scanner, Pratt parser, and create the EntityFramework WHERE clause from the parsed syntax tree but now that it is working, filtering is easy and actually fun…
Maybe the json:api team should consider something like this as another possible “recommendation” or “proposal” for filtering, or not…