Representing tree data that includes resource links?

How would you go about representing tree-structured data that contains links to other resources?

I want to expose a document’s hierarchical table of contents, which contains both section headings (not resources)* and sub-documents (resources).

Example:

Book 1           (heading)
> ... bla ...    (sub-document)
> Chapter 1      (heading)
> > ... bla ...  (sub-document)
Book 2           (heading)
> ... bla ...    (sub-document)
> ... bla ...    (sub-document)

My first thought was to have a “tableOfContents” attribute, but I think links are not allowed in attributes?

    {
      "data": {
        "id": 1,
        "type": "containerDocument",
        "attributes": {
          "tableOfContents": [
            {
              "type": "sectionHeading",
              "attributes": { "title": "Book 1" },
              "children": [
                {
                  "type": "subDocument",
                  "id": "1234",
                  "links": {
                    "self": "/subDocuments/1234"
                  }
                },
                {
                  "type": "sectionHeading",
                  "attributes": { "title": "Chapter 1" },
                  "children": [
                    {
                      "type": "subDocument",
                      "id": "5678",
                      "links": {
                        "self": "/subDocuments/5678"
                      }
                    }
                  ]
                }
              ]
            },
            {
              "type": "sectionHeading",
              "attributes": { "title": "Book 2" },
              "children": [
                {
                  "type": "subDocument",
                  "id": "9012",
                  "links": {
                    "self": "/subDocuments/9012"
                  }
                },
                {
                  "type": "subDocument",
                  "links": {
                    "self": "/subDocuments/3456"
                  }
                }
              ]
            }
          ]
        }
      }
    }    

Can I model this as a relationship instead? If so, how?

The sub-documents are accessible via their own URL routes and it would be nice to have them include-able in requests for the table of contents resource.

Any help is much appreciated!

*(My data source does have IDs for the headings, so they could theoretically be resources…)

A common concept nowadays seem to be to represent a document as a linear series of “blocks”, where a block can be anything. For example, a heading is a block, a paragraph is a block, a list is a block, an image is a block, etc., and each block type can have different data associated with them (headings and paragraphs have text, lists have list items, etc.). I have never seen this represented using JSON:API, just a normal JSON structure. But maybe it can serve as some inspiration. See e.g. Editor.js. Wordpress also works like this (I don’t know the internals, though).

Hm, that’s an interesting approach. Linearising would simplify the structure in the API greatly.

However, it would put the burden on the client to reconstruct the tree structure of the table of contents to allow features such as expanding/collapsing subtrees (chapters/sections). It seems a waste to flatten something that comes tree-structured in the first place only to have to reconstruct the tree later on…

JSON:API is flat by design. If you use SQL, chances are it’s flat in your DB too (if you save the “blocks” as separate rows, at least). IMHO it’s not a problem to require clients to construct trees based on parent or children relationships in a flat list. I don’t think the “tree creation” itself is complicated.

In any case, keep in mind that JSON:API does not support nested/embedded resources (as you give an example of in your post). If you want the separate blocks/sections as separate resources, then a flat list is the only allowed way. The alternative is to not have them as separate resources, but just have an attribute that is a complex JSON object with the entire structure (no resource stuff there). That may work well if it’s read-only, but may be quite horrible if you need to support updates, since JSON:API does not support partial updates of complex object-typed attributes (that’s when you use separate resources instead).

Ok, thanks for the clarification re: nested resources! I wasn’t sure after reading the spec whether that was the case. I’ll see if we can work with a flattened version or need to pursue another approach.

A complex attribute probably wouldn’t work: the leaf nodes of the tree need to be individually addressable.