JsonApiResponseAdapter
Unwrap strict JSON:API <https://jsonapi.org>_ response envelopes for ApiBackend.
Overview
Many public APIs follow the JSON:API specification, which wraps every response in a well-defined envelope. A list response looks like this:
{
"data": [
{"id": "1", "type": "articles", "attributes": {"title": "Hello", "body": "World"}},
{"id": "2", "type": "articles", "attributes": {"title": "Bye", "body": "Now"}}
]
}
And a single-resource response like this:
{
"data": {"id": "42", "type": "articles", "attributes": {"title": "Hello", "body": "World"}}
}
Without an adapter, ApiBackend sees the outer {"data": [...]} shell and tries to map it directly to your model columns — which never works. JsonApiResponseAdapter peels that shell away and flattens each resource object so that clearskies sees a plain dictionary:
{"id": "1", "type": "articles", "title": "Hello", "body": "World"}
Normalisation rules
Each JSON:API resource object is transformed as follows:
- All fields inside
attributesare promoted to the top level. - The resource-level
idandtypeare preserved and always take precedence over any field of the same name that may exist insideattributes. relationships,links, andmetaare dropped — they are not part of the flat column mapping layer.
Full working example
Define a backend subclass that points at the upstream JSON:API service and attach the adapter, then wire it into a model and expose it through a list endpoint:
import clearskies
class ArticleBackend(clearskies.backends.ApiBackend):
def __init__(self):
super().__init__(
base_url="https://jsonapi.example.com",
response_adapter=clearskies.backends.adapters.JsonApiResponseAdapter(),
)
class Article(clearskies.Model):
id_column_name = "id"
backend = ArticleBackend()
id = clearskies.columns.String()
title = clearskies.columns.String()
body = clearskies.columns.String()
wsgi = clearskies.contexts.WsgiRef(
clearskies.endpoints.List(
model_class=Article,
readable_column_names=["id", "title", "body"],
sortable_column_names=["id"],
default_sort_column_name=None,
default_limit=10,
),
classes=[Article],
)
wsgi()
Assuming https://jsonapi.example.com/articles returns a JSON:API envelope, the endpoint unwraps it transparently:
$ curl 'http://localhost:8080/' | jq
{
"status": "success",
"error": "",
"data": [
{"id": "1", "title": "Hello", "body": "World"},
{"id": "2", "title": "Bye", "body": "Now"}
],
"pagination": {"number_results": 2, "limit": 10, "next_page": {}},
"input_errors": {}
}
Binding the adapter via DI
You can also register the adapter in the dependency injection container rather than setting it on the backend instance directly. The ApiBackend will pick it up automatically:
context = clearskies.contexts.WsgiRef(
clearskies.endpoints.List(model_class=Article, ...),
bindings={"response_adapter": JsonApiResponseAdapter()},
)
Note that an adapter set directly on the backend always wins over one registered via DI.