Source code for gamesheet_sdk.admin.shared.jsonapi
# Copyright (c) 2026 bdperkin
# SPDX-License-Identifier: MIT
"""JSON:API parsing utilities."""
from __future__ import annotations
from typing import Any
[docs]
def parse_jsonapi_resource(
item: dict[str, Any],
relationship_map: dict[str, str] | None = None,
) -> dict[str, Any]:
"""Parse a JSON:API resource into a flat dictionary.
Flattens a JSON:API resource object into a simple dict by merging
the id, attributes, and optionally extracting relationship IDs.
**JSON:API structure:**
.. code-block:: json
{
"id": "123",
"type": "leagues",
"attributes": {"title": "NHL", ...},
"relationships": {"association": {"data": {"id": "456"}}}
}
**Result:**
.. code-block:: python
{"id": "123", "title": "NHL", "association_id": "456"}
:param item: JSON:API resource object.
:type item: dict[str, Any]
:param relationship_map: Maps relationship names to attribute keys. Example: ``{"association":
"association_id"}``.
:type relationship_map: dict[str, str] | None
:returns: Flattened dict with id + attributes + relationship IDs.
:rtype: dict[str, Any]
"""
result: dict[str, Any] = {"id": item["id"]}
result.update(item.get("attributes", {}))
if relationship_map:
for rel_name, attr_key in relationship_map.items():
result[attr_key] = extract_relationship_id(item, rel_name)
return result
[docs]
def build_invitation_code_lookup(body: dict[str, Any]) -> dict[str, str]:
"""Build invitation code lookup from JSON:API included resources.
Parses the included section of a JSON:API response to extract invitation codes keyed by invitation ID.
:param body: The JSON:API response body containing included resources.
:type body: dict[str, Any]
:returns: Dictionary mapping invitation IDs to invitation codes.
:rtype: dict[str, str]
"""
invitation_codes: dict[str, str] = {}
for item in body.get("included", []):
if item.get("type") == "invitations":
invitation_id = item.get("id")
code = item.get("attributes", {}).get("code")
if invitation_id and code:
invitation_codes[invitation_id] = code
return invitation_codes
[docs]
def get_invitation_code_from_relationship(
item: dict[str, Any],
invitation_codes: dict[str, str],
) -> str | None:
"""Extract invitation code from team's invitation relationship.
The invitations relationship can be either a single object or an array of objects.
:param item: The JSON:API team resource object.
:type item: dict[str, Any]
:param invitation_codes: Lookup dictionary from invitation IDs to codes.
:type invitation_codes: dict[str, str]
:returns: The invitation code if found, otherwise None.
:rtype: str | None
"""
inv_rel = item.get("relationships", {}).get("invitations", {}).get("data")
if inv_rel:
# invitations relationship can be single object or array
inv_id = inv_rel[0]["id"] if isinstance(inv_rel, list) else inv_rel.get("id")
if inv_id and inv_id in invitation_codes:
return invitation_codes[inv_id]
return None