Skip to content

SF: Sparse fieldsets cannot reach fields inside collections, and path existence depends on the data #154

Closed

Description

@MoonWorm

SF: Sparse fieldsets cannot reach fields inside collections, and path existence depends on the data

Two gaps in how requested paths are resolved, both silent, both leaving the plugin inconsistent with
access control.

Container elements. fields[users]=addresses.zip names a field of every address, not of one, but
getAllFieldPaths never descended into element types — a container contributed its own path and
nothing below it. The requested path therefore did not exist, so the request tripped
requestedFieldsDontExistMode instead of narrowing anything. Access control resolves requirements
inside containers as of the previous change, so the two plugins gave different answers to the same
question about the same model.

Path existence decided from the instance. fieldPathExists answered by reading the value and
catching the failure, so fields[users]=address.city was reported as non-existent whenever that
particular resource happened to carry a null address — the same request succeeding or failing
depending on the row it landed on. It is now decided from the type, which is what "does this field
exist" was always meant to ask.

getAllFieldPaths now descends into the element type of a container, and SparseFieldsetsHelper
narrows every element against the same paths. Containers moves from the access control plugin to
jsonapi4j-base, so both plugins share one notion of what a container is and how to rebuild one.

Elements are never dropped. Sparse fieldsets narrows what each object carries, not how many objects
there are, so a container keeps its length and its indices — deliberately unlike access control, where
an element the caller may not see at all is removed. A container that cannot be rebuilt is logged and
returned unchanged rather than failing the request; nothing has been hidden, so there is nothing to
fail closed over.

Test profiles ran the two plugins in isolation. The access control profile disabled sparse
fieldsets and the sparse fieldsets profile disabled access control, so their interaction had no
coverage anywhere — including the question of whether naming a field explicitly can talk access control
out of hiding it. The Quarkus profile also misspelled the property as jsonapi4j.sf.enable, which
never bound, so sparse fieldsets silently ran there while it was off in Spring Boot and Servlet: the
three sample apps were running the shared access control suite against different plugin sets.

Verification. Five new unit tests in SparseFieldsetsHelperTests covering element narrowing, the
original left unmodified, an unrequested container excluded whole, and a path through a null
intermediate. Four new scenarios in the shared sample-app suite across Spring Boot, Quarkus and
Servlet: the element path on a multiple-resources read and on included resources — each goes through a
different visitor — and, with both plugins enabled together for the first time, a denied field
requested explicitly staying hidden, plus its granted counterpart, which is what proves sparse
fieldsets is running rather than passing vacuously. Reverting the element descent fails exactly two
unit tests and three sample-suite tests and nothing else.

Activity

  1. added a commit that references this issue on Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions