Filtering

Requests that would normally return a collection of resources can be filtered so that only a subset of the resources are returned in a response. If the client specifies the filter[objects] query parameter, it must be a URL encoded JSON list of filter objects, as described below.

Quick client examples for filtering

The following are some quick examples of making filtered GET requests from different types of clients. More complete documentation is in subsequent sections. In these examples, each client will filter by instances of the model Person whose names contain the letter “y”.

Using the Python requests library:

import requests
import json

url = 'http://127.0.0.1:5000/api/person'
headers = {'Accept': 'application/vnd.api+json'}

filters = [dict(name='name', op='like', val='%y%')]
params = {'filter[objects]': json.dumps(filters)}

response = requests.get(url, params=params, headers=headers)
assert response.status_code == 200
print(response.json())

Using jQuery:

var filters = [{"name": "id", "op": "like", "val": "%y%"}];
$.ajax({
  data: {"filter[objects]": JSON.stringify(filters)},
  headers: {
    "Accept": JSONAPI_MIMETYPE
  },
  success: function(data) { console.log(data.objects); },
  url: 'http://127.0.0.1:5000/api/person'
});

Using curl:

curl \
  -G \
  -H "Accept: application/vnd.api+json" \
  -d "filter[objects]=[{\"name\":\"name\",\"op\":\"like\",\"val\":\"%y%\"}]" \
  http://127.0.0.1:5000/api/person

The examples/ directory has more complete versions of these examples.

Filter objects

A filter object is a JSON object. Filter objects are defined recursively as follows. A filter object may be of the form

{"name": <field_name>, "op": <unary_operator>}

where <field_name> is the name of a field on the model whose instances are being fetched and <unary_operator> is the name of one of the unary operators supported by Flask-Restless. For example,

{"name": "birthday", "op": "is_null"}

A filter object may be of the form

{"name": <field_name>, "op": <binary_operator>, "val": <argument>}

where <binary_operator> is the name of one of the binary operators supported by Flask-Restless and <argument> is the second argument to that binary operator. For example,

{"name": "age", "op": "gt", "val": 23}

A filter object may be of the form

{"name": <field_name>, "op": <binary_operator>, "field": <field_name>}

The field element indicates that the second argument to the binary operator should be the value of that field. For example, to filter by resources that have a greater width than height,

{"name": "width", "op": "gt", "field": "height"}

A filter object may be of the form

{"name": <relation_name>, "op": <relation_operator>, "val": <filter_object>}

where <relation_name> is the name of a relationship on the model whose resources are being fetched, <relation_operator> is either "has", for a to-one relationship, or "any", for a to-many relationship, and <filter_object> is another filter object. For example, to filter person resources by only those people that have authored an article dated before January 1, 2010,

{
  "name": "articles",
  "op": "any",
  "val": {
    "name": "date",
    "op": "lt",
    "val": "2010-01-01"
  }
}

For another example, to filter article resources by only those articles that have an author of age at most fifty,

{
  "name": "author",
  "op": "has",
  "val": {
    "name": "age",
    "op": "lte",
    "val": 50
  }
}

A filter object may be a conjunction (“and”), disjunction (“or”), or negation (“not”) of other filter objects:

{"or": [<filter_object>, <filter_object>, ...]}

or

{"and": [<filter_object>, <filter_object>, ...]}

or

{"not": <filter_object>}

For example, to filter by resources that have width greater than height, and length of at least ten,

{
  "and": [
    {"name": "width", "op": "gt", "field": "height"},
    {"name": "length", "op": "lte", "val": 10}
  ]
}

How are filter objects used in practice? To get a response in which only those resources that meet the requirements of the filter objects are returned, clients can make requests like this:

GET /api/person?filter[objects]=[{"name":"age","op":"<","val":18}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

Operators

Flask-Restless understands the following operators, which correspond to the appropriate SQLAlchemy column operators.

  • ==, eq, equals, equals_to
  • !=, neq, does_not_equal, not_equal_to
  • >, gt, <, lt
  • >=, ge, gte, geq, <=, le, lte, leq
  • in, not_in
  • is_null, is_not_null
  • like, ilike, not_like
  • has
  • any

Flask-Restless also understands the PostgreSQL network address operators <<, <<=, >>, >>=, <>, and &&.

Warning

If you use a percent sign in the argument to the like operator (for example, %somestring%), make sure it is percent-encoded, otherwise the server may interpret the first few characters of that argument as a percent-encoded character when attempting to decode the URL.

Custom operators

You can use the register_operator() function to extend the set of known operators:

from flask_restless import register_operator

# Create a custom "greater than" implementation.
register_operator('my_gt', lambda x, y: x - y > 0)

Then the client makes a request with a filter object whose op element is the name of this operator:

GET /api/person?filter[objects]=[{"name":"age","op":"my_gt","val":18}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

You can also override existing operators by setting the name of your operator to be the name of a existing operator; the built-in operators are listed in the previous section:

register_operator('gt', lambda x, y: x - y > 0)

Simpler filtering

Flask-Restless also supports a simpler form of filtering as described in the JSON API filtering recommendation. For filtering by the foreign key of a to-one relationship, use a request of the form

GET /api/comments?filter[post]=1,2&filter[author]=12 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

Flask-Restless will automatically determine the correct query corresponding to the given to-one relationships.

You can also filter by attribute:

GET /api/person?filter[age]=21 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

Implementation note

Each of these simple filters is converted to the more complex filter object representation as described in the preceding sections and appended to the list of filter objects computed from the request query parameters.

Requiring singleton collections

If a client wishes a request for a collection to yield a response with a singleton collection, the client can use the filter[single] query parameter. The value of this parameter must be either 1 or 0. If the value of this parameter is 1 and the response would yield a collection of either zero or more than two resources, the server instead responds with 404 Not Found.

For example, a request like

GET /api/person?filter[single]=1&filter[objects]=[{"name":"id","op":"eq","val":1}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "type": "person",
    "links": {
      "self": "http://example.com/api/person/1"
    }
  },
  "links": {
    "self": "http://example.com/api/person?filter[single]=1&filter[objects]=[{\"name\":\"id\",\"op\":\"eq\",\"val\":1}]"
  },
}

But a request like

GET /api/person?filter[single]=1 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

would yield an error response if there were more than one Person instance in the database.

Filter object examples

Attribute greater than a value

On request

GET /api/person?filter[objects]=[{"name":"age","op":"gt","val":18}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Person instances that have age attribute greater than or equal to 18:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "age": 19
      },
      "id": "2",
      "links": {
        "self": "http://example.com/api/person/2"
      },
      "type": "person"
    },
    {
      "attributes": {
        "age": 29
      },
      "id": "5",
      "links": {
        "self": "http://example.com/api/person/5"
      },
      "type": "person"
    },
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"name\":\"age\",\"op\":\"gt\",\"val\":18}]"
  },
  "meta": {
    "total": 2
  }
}

Arbitrary Boolean expression of filters

On request

GET /api/person?filter[objects]=[{"or":[{"name":"age","op":"lt","val":10},{"name":"age","op":"gt","val":20}]}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Person instances that have age attribute either less than 10 or greater than 20:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "age": 9
      },
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "type": "person"
    },
    {
      "attributes": {
        "age": 25
      },
      "id": "3",
      "links": {
        "self": "http://example.com/api/person/3"
      },
      "type": "person"
    }
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"or\":[{\"name\":\"age\",\"op\":\"lt\",\"val\":10},{\"name\":\"age\",\"op\":\"gt\",\"val\":20}]}]"
  },
  "meta": {
    "total": 2
  }
}

Comparing two attributes

On request

GET /api/box?filter[objects]=[{"name":"width","op":"ge","field":"height"}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Box instances that have width attribute greater than or equal to the value of the height attribute:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "height": 10,
        "width": 20
      }
      "id": "1",
      "links": {
        "self": "http://example.com/api/box/1"
      },
      "type": "box"
    },
    {
      "attributes": {
        "height": 15,
        "width": 20
      }
      "id": "2",
      "links": {
        "self": "http://example.com/api/box/2"
      },
      "type": "box"
    }
  ],
  "links": {
    "self": "/api/box?filter[objects]=[{\"name\":\"width\",\"op\":\"ge\",\"field\":\"height\"}]"
  },
  "meta": {
    "total": 100
  }
}

Using has and any

On request

GET /api/person?filter[objects]=[{"name":"articles","op":"any","val":{"name":"date","op":"lt","val":"2010-01-01"}}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those people that have authored an article dated before January 1, 2010 (assume in the example below that at least one of the article linkage objects refers to an article that has such a date):

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "relationships": {
        "articles": {
          "data": [
            {
              "id": "1",
              "type": "article"
            },
            {
              "id": "2",
              "type": "article"
            }
          ],
          "links": {
            "related": "http://example.com/api/person/1/articles",
            "self": "http://example.com/api/person/1/relationships/articles"
          }
        }
      },
      "type": "person"
    }
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"name\":\"articles\",\"op\":\"any\",\"val\":{\"name\":\"date\",\"op\":\"lt\",\"val\":\"2010-01-01\"}}]"
  },
  "meta": {
    "total": 1
  }
}

On request

GET /api/article?filter[objects]=[{"name":"author","op":"has","val":{"name":"age","op":"lte","val":50}}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those articles that have an author of age at most fifty (assume in the example below that the author linkage objects refers to a person that has such an age):

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "links": {
        "self": "http://example.com/api/article/1"
      },
      "relationships": {
        "author": {
          "data": {
            "id": "7",
            "type": "person"
          },
          "links": {
            "related": "http://example.com/api/article/1/author",
            "self": "http://example.com/api/article/1/relationships/author"
          }
        }
      },
      "type": "article"
    }
  ],
  "links": {
    "self": "/api/article?filter[objects]=[{\"name\":\"author\",\"op\":\"has\",\"val\":{\"name\":\"age\",\"op\":\"lte\",\"val\":50}}]"
  },
  "meta": {
    "total": 1
  }
}