Skip to content

[13.x] Honor JSON:API sparse fieldsets for relationships#60897

Open
yazansalhi wants to merge 1 commit into
laravel:13.xfrom
yazansalhi:honor-sparse-fieldsets-for-relationships
Open

[13.x] Honor JSON:API sparse fieldsets for relationships#60897
yazansalhi wants to merge 1 commit into
laravel:13.xfrom
yazansalhi:honor-sparse-fieldsets-for-relationships

Conversation

@yazansalhi

Copy link
Copy Markdown

Sparse fieldsets are currently applied only to a resource object's attributes. Relationships are not filtered, so a request that restricts the fields for a type still receives that type's relationships:

GET /users/1?include=posts&fields[users]=name
{
    "data": {
        "id": "1",
        "type": "users",
        "attributes": { "name": "Taylor" },
        "relationships": {
            "posts": { "data": [{ "id": "1", "type": "posts" }] }
        }
    },
    "included": [ ... ]
}

posts was not requested in fields[users], so it should not appear.

The specification defines a resource object's attributes and relationships collectively as its fields:

A resource object's attributes and its relationships are collectively called its "fields".
Resource Objects

and requires that a restricted fieldset be respected:

If a client requests a restricted set of fields for a resource type, an endpoint MUST NOT include additional fields in resource objects of that type in its response.
Sparse Fieldsets

This applies the fieldset to relationships using the same usesRequestQueryString / hasSparseFieldset checks already used for attributes, so ignoreFieldsAndIncludesInQueryString() continues to opt out of both, and an empty fieldset (fields[users]=) drops relationships just as #59813 made it drop attributes.

After the change the response is:

{
    "data": {
        "id": "1",
        "type": "users",
        "attributes": { "name": "Taylor" }
    },
    "included": [ ... ]
}

Requesting the relationship keeps it, as expected:

GET /users/1?include=posts&fields[users]=name,posts

Scope notes

  • included is deliberately untouched. It is governed by the include parameter, not by fields, so the related resources are still returned (with their own type's fieldset applied to them).
  • resolveResourceRelationshipIdentifiers() takes the resource type as an optional second parameter rather than a required one, so any application overriding this protected method keeps working on 13.x. resolveResourceObject() passes the type it has already resolved, so there is no extra work per resource.

Tests

Two tests added to JsonApiResourceTest: one asserting an unrequested relationship is omitted, one asserting a requested relationship is retained. I confirmed the first fails without the source change and passes with it.

Locally: tests/Http and tests/Integration/Http pass (770 tests, 2358 assertions), and Pint and PHPStan (phpstan.src.neon.dist) both report no issues.

Sparse fieldsets were only applied to a resource object's attributes, so
a request such as ?include=posts&fields[users]=name still returned the
posts relationship on the users resource.

The JSON:API specification defines a resource object's attributes and
its relationships collectively as its fields, and states that when a
client requests a restricted set of fields for a resource type, the
server must not include additional fields in resource objects of that
type. Apply the fieldset to relationships as well.

Included resources are unaffected, since those are governed by the
include parameter rather than by fields.
@crynobone
crynobone requested a review from timacdonald July 27, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant