Parse JSON:API queries, includes, and sparse fieldsets¶
Add com.kazforge:jsonapi-java-query at the same version as your adapter. This module has no
Jackson or HTTP framework dependency. Use the article/people write model and ArticleView from
resources.
Parse a request¶
For a query string (requestQuery) such as:
Parse the requested selection and opaque parameters:
import com.kazforge.jsonapi.query.JsonApiQuery;
import com.kazforge.jsonapi.query.JsonApiQueryParser;
JsonApiQueryParser parser = new JsonApiQueryParser();
JsonApiQuery query = parser.parseRaw(requestQuery);
Conceptual summary of the parsed values (not JsonApiQuery.toString() output):
include: author
fields[articles]: title, author
sort: title (descending)
page[number]: [2]
filter[status]: [published]
These are requested values, not an executed persistence query.
For framework parameters that are already decoded, use parseDecoded; do not URL-decode twice:
import java.util.List;
import java.util.Map;
Map<String, List<String>> parameters = Map.of(
"include", List.of("author"),
"fields[articles]", List.of("title,author")
);
JsonApiQuery selected = new JsonApiQueryParser().parseDecoded(parameters);
include, each fields[TYPE], and sort require one value occurrence, with comma-separated
tokens inside that value. Names are exact JSON:API tokens, not trimmed Java property names.
Explicit include= or fields[articles]= stays distinct from omission. QueryAllowList can reject
unpermitted include, field, or sort tokens, but a successful parse grants no access to a resource or field.
Apply selection with policy¶
Use selectedApi from the representation policy configuration,
which permits article-author inclusion and the requested fields. Configure it once on the runtime,
then pass selection separately for each write.
Use the decoded selection for this write, with the author's data already available:
import com.kazforge.jsonapi.api.ResourceWriteOptions;
Person ada = new Person("p1", "Ada");
ArticleWithAuthor article = new ArticleWithAuthor(
"1",
"Working with JSON:API",
ada
);
ResourceWriteOptions options = ResourceWriteOptions.defaults()
.withSelection(selected.selection());
String json = selectedApi.resources().writeOne(article, options);
Result, formatted for display:
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "Working with JSON:API"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "p1"
}
}
}
},
"included": [
{
"type": "people",
"id": "p1",
"attributes": {
"name": "Ada"
}
}
]
}
Read the primary DTO and included core resources together:
import com.kazforge.jsonapi.api.ResourceDocument;
import com.kazforge.jsonapi.core.model.ResourceObject;
ResourceDocument<ArticleView> result = selectedApi.resources().readOneDocument(
json,
ArticleView.class
);
ArticleView primaryArticle = result.resource();
List<ResourceObject> included = result.included();
primaryArticle.author() still contains only the people / p1 identifier. included contains
the separate person resource with Ada's name; it is not injected into the DTO's relationship.
Selection belongs to this write; it does not replace runtime policy. Inclusion does not cause persistence fetching: supply already available objects or implement application lookup before writing.
Selection is not permission
An include request cannot override application representation policy. Policy is not a substitute
for endpoint/resource/field authorization. Do not copy an unrestricted allowAll() policy into a
public endpoint merely to make a query work.
Sparse fieldsets shape output per resource type. Inclusion and linkage remain separate: requesting
included can retain resources even when a fieldset omits their linking relationship.
What stays with the application¶
| Parameter | Library result | Application work |
|---|---|---|
include, fields[TYPE] |
Representation selection | Authorize, load permitted data, apply runtime policy |
sort |
Ordered field/direction values | Map permitted names to actual query ordering |
page, filter, unknown parameters |
Ordered opaque value lists | Define semantics, validate values, execute queries |
Parsing preserves repeated opaque values and encounter order. It does not calculate page links,
interpret filters, run sorting, or choose an HTTP status. Query failures use JsonApiQueryException
with diagnostic() and an optional parameterName(), separate from document/mapping failures.
See query attribution for decoded parameter names and
application-owned error responses for
selective use of source.parameter.
See JsonApiQueryParser
and RepresentationPolicy
for exact parsing and policy contracts.