"Countable" Profile¶
Introduction¶
This is the specification of a
profile for the JSON:API
specification. The URL for this profile is
https://haddowg.github.io/json-api/profiles/countable/.
A collection's rendered data tells a client which resources are present, but not how many there are in total when the data is not fully materialised — a paginated primary collection, or a to-many relationship rendered as links-only or paginated, does not reveal the size of its set. A client that needs the count ("the article, and how many comments it has"; "how many articles match this filter") must otherwise fetch the whole collection and count it, or page to the end.
This profile defines a single query-parameter family, withCount, that lets a
client ask for the size of a countable collection alongside the primary resource
— a named relationship's set, and/or the primary collection itself via the
reserved _self_ token. The server returns each count as a total member on the
named relationship object's meta (for a relation) or on the top-level meta (for
_self_). For example:
GET /articles/1?withCount=comments
Accept: application/vnd.api+json;profile="https://haddowg.github.io/json-api/profiles/countable/"
renders article 1 with data.relationships.comments.meta.total set to the number
of comments, in one request and without materialising the comments; and
GET /articles?page[size]=2&withCount=_self_
Accept: application/vnd.api+json;profile="https://haddowg.github.io/json-api/profiles/countable/"
renders page 1 of the articles collection with the top-level meta.total set to the
total number of matching articles (and, since the collection is paginated, the server
MAY also surface that total as meta.page.total in the base pagination meta).
Conventions¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. This is the same interpretation the JSON:API specification applies to these key words; see its conventions.
Specification¶
Concepts¶
Countable collections¶
Counting is a property a server makes a collection expose. Two kinds of collection can be made countable:
- a to-many relationship, named directly (
?withCount=<rel>). A to-one relationship is never countable — its linkage is a single resource identifier, not a set. - the primary collection, named by the reserved token
_self_(?withCount=_self_). This counts the current request's primary data: the collection of aGET /<type>(orGET /<type>/<id>/<rel>related endpoint), gated on that resource/relation being countable.
Whether any given collection is countable is entirely at the server's discretion; this
profile does NOT require counting to be opt-in. A server MAY make every to-many
relationship and every primary collection countable, or none, or any subset. The
profile's only obligation is that a withCount naming a target the server has not made
countable is rejected (see Error Cases). The two kinds compose:
?withCount=_self_,comments.
The count reflects the rendered set¶
The total a server reports MUST equal the size of the same set the collection
would return for the request — that is, it MUST honour any filtering the server
applies (default or request-supplied). It is the count of the filtered set, not of
raw membership. For a relationship it agrees with the total the relationship's own
related-collection endpoint would report for an equivalent request; for _self_ it
is the total of the filtered primary collection.
One count, two slots¶
For a _self_ count the server MUST expose the total as the top-level
meta.total (the universal cardinality slot). When the primary collection is
paginated, a server MAY also surface the same total in the base pagination meta
(meta.page.total); doing so is governed by the base specification's pagination
rules, not by this profile. When both meta.total and meta.page.total are present
for the same collection, they MUST be equal.
Query Parameters¶
This profile reserves one implementation-specific query-parameter family:
| Family base | Role |
|---|---|
withCount |
canonical |
The family base withCount contains a non-a-z character (an uppercase letter),
satisfying the base specification's naming rule for an implementation-specific
query-parameter family. An implementation of this profile MUST use this exact
family name.
withCount is a flat, comma-separated list — the same shape as the
include parameter — where each
member is either a relationship name of the primary resource to be counted, or
the reserved token _self_ naming the primary collection itself:
This profile reserves the literal token _self_ to name the primary collection within
withCount. A server implementing this profile MUST interpret a _self_ member as
the primary-collection count and MUST NOT treat it as a relationship name;
consequently _self_ is not a usable relationship name under this profile. A client
MUST spell the primary-collection target exactly _self_.
Each named target that the primary resource exposes as countable is counted; order
is not significant and duplicate names are equivalent to a single mention. A named
relationship that is not countable, is a to-one, or does not exist — or _self_
against a resource that is not countable — is an error (see
Error Cases).
Processing¶
Profile negotiation¶
This profile is advisory and opt-in. It is negotiated only by listing its URI in
the profile media-type parameter of the request Accept header, per
content negotiation for profiles; this
profile defines no other negotiation channel (a profile query parameter or Link
does not by itself enable withCount). A server MUST parse and apply the
withCount family only when the client has negotiated this profile in that way.
When the profile is not negotiated, the server MUST NOT ascribe this profile's
meaning to withCount; per the base specification a server MAY either ignore the
parameter OR reject it as an unrecognized query parameter (400 Bad Request). A
client therefore MUST NOT rely on withCount having any effect unless it has
negotiated the profile.
Counting¶
For each negotiated, valid relationship name, the server MUST add a total
member to that relationship object's meta whenever the relationship object is
rendered — on a single primary resource, on every member of a primary collection,
and on a related resource's relationship object. For a negotiated, valid _self_,
the server MUST add the total of the primary collection to the document's
top-level meta.total (and MAY additionally surface it as meta.page.total when
the collection is paginated, per One count, two slots). A
target not named in withCount MUST NOT carry a total, even when it is
countable: the count is gated by the request, not emitted by default.
A collection a server has not paginated is fetched whole, so its size is already known and counting it is free. A server MAY render
meta.totalfor an unpaginated primary collection (or a materialised to-many relationship) unconditionally — without_self_/?withCountand without an extra query — since the count costs nothing.
Advertising an applied profile¶
When a server applies this profile to a response, it MUST advertise the
profile as the base specification requires: the profile URI MUST be present in
the profile parameter of the response Content-Type media type and in the
document's top-level jsonapi.profile array.
Document Structure¶
This profile reserves the total member of a relationship object's meta
object (for a relation count) and of the document's top-level meta object (for
a _self_ count). When present, total MUST be a non-negative integer giving the
size of the (filtered) set. The same number MAY additionally appear as
meta.page.total when the collection is paginated (the base pagination meta). The
profile reserves no other document members and adds nothing else to the response body.
Error Cases¶
A server MUST validate the full requested withCount set before rendering, and
MUST NOT render a partial document and then fail. If any requested target is
invalid, the server MUST reject the whole request before producing the document.
Each of the following is a client error: a target that does not exist on the
primary resource, a target that is a to-one relationship, or a target the server
has not declared countable (including _self_ against a resource that is not
countable). For any such case the server MUST respond 400 Bad Request, following
the base specification's rules for
processing errors, with an
error object whose source.parameter
is withCount.
Notes¶
withCount=<rel>reports the count thatGET /<primary>/<id>/<rel>(the relationship's related resource collection) would page under the same filtering, folded onto the primary response asrelationships.<rel>.meta.total— without materialising the related collection.withCount=_self_reports the total of the primary collection (aGET /<type>or a related endpoint), folded onto the top-levelmeta.total(and the same total MAY also appear asmeta.page.totalwhen paginated) — for a server that already computes the total for its own pagination, this is the same total it would otherwise expose through the base pagination meta.