Required field of included resources

I am designing an API based on json:api to manage time tables.

Trying to utilize compound documents, I became curious about whether included documents (under the toplevel member "included") must have all fields as when they are fetched from it’s canonical address.

For example, when I fetch a canonical endpoint of an student, the full response contains attributes(name, code number, etc), and also contains time table (which is specified by relationship). Also, when I fetch a canonical endpoint of a class, the full response contains attributes and also contains a list of students that attend to class (which is also specified by relationship). I would like to include documents of all students, but without relationship for time tables.

This is when I GET an endpoint for a student:

GET /students/18113

{
    "data": {
        "type": "student",
        "id": "18113",
        "attributes": {
            "name": "조성빈",
            "phone": "[omitted]",
            "email": "[omitted]"
        },
        "relationships": {
            "classes": {
                "data": [{
                    "type": "class",
                    "id": "16"
                }, {
                    "type": "class",
                    "id": "19"
                }, {
                    "type": "class",
                    "id": "34"
                }]
            }
        },
        "links": {
            "self": "/students/18113"
        }
    }
}

and this is what I would like to respond when I GET an endpoint for a class:

GET /classes/16
{
    "data": {
        "type": "class",
        "id": "16",
        "relationships": {
            "students": {
                "links": {
                    "self": "/classes/16/relationship/students",
                    "related": "/classes/16/students"
                },
                "data": [{
                    "type": "student",
                    "id": "18034"
                }, {
                    "type": "student",
                    "id": "18112"
                }, {
                    "type": "student",
                    "id": "18113"
                }, {
                    "type": "student",
                    "id": "18127"
                }, {
                    "type": "student",
                    "id": "17023"
                }]
            }
        },
        "links": {
            "self": "/classes/16"
        }
    },
    "included": [{
        "type": "student",
        "id": "18034",
        "attributes": {
            "name": "김한서",
            "email": null,
            "phone": "[omitted]"
        }
    }, {
        "type": "student",
        "id": "18112",
        "attributes": {
            "name": "정현석",
            "email": "[omitted]",
            "phone": "[omitted]"
        }
    }, {
        "type": "student",
        "id": "18113",
        "attributes": {
            "name": "조성빈",
            "email": "[omitted]",
            "phone": "[omitted]"
        }
    }, {
        "type": "student",
        "id": "17023",
        "attributes": {
            "name": "김홍녕",
            "email": "[omitted]",
            "phone": "[omitted]"
        }
    }]
}

instead of:

GET /classes/16
{
    "data": {
        "type": "class",
        "id": "16",
        "relationships": {
            "students": {
                "links": {
                    "self": "/classes/16/relationship/students",
                    "related": "/classes/16/students"
                },
                "data": [{
                    "type": "student",
                    "id": "18034"
                }, {
                    "type": "student",
                    "id": "18112"
                }, {
                    "type": "student",
                    "id": "18113"
                }, {
                    "type": "student",
                    "id": "18127"
                }, {
                    "type": "student",
                    "id": "17023"
                }]
            }
        },
        "links": {
            "self": "/classes/16"
        }
    },
    "included": [{
        "type": "student",
        "id": "18034",
        "attributes": {
            "name": "김한서",
            "email": null,
            "phone": "[omitted]"
        },
        "relationships": {
            "classes": {
                "data": [{
                    "type": "class",
                    "id": "16"
                }]
            }
        }
    }, {
        "type": "student",
        "id": "18112",
        "attributes": {
            "name": "정현석",
            "email": "[omitted]",
            "phone": "[omitted]"
        },
        "relationships": {
            "classes": {
                "data": [{
                    "type": "class",
                    "id": "16"
                }]
            }
        }
    }, {
        "type": "student",
        "id": "18113",
        "attributes": {
            "name": "조성빈",
            "email": "[omitted]",
            "phone": "[omitted]"
        },
        "relationships": {
            "classes": {
                "data": [{
                    "type": "class",
                    "id": "16"
                }, {
                    "type": "class",
                    "id": "19"
                }, {
                    "type": "class",
                    "id": "34"
                }]
            }
        }
    }, {
        "type": "student",
        "id": "17023",
        "attributes": {
            "name": "김홍녕",
            "email": "[omitted]",
            "phone": "[omitted]"
        },
        "relationships": {
            "classes": {
                "data": [{
                    "type": "class",
                    "id": "16"
                }]
            }
        }
    }]
}

Is this compliant to the spec as well?
Is this not compliant, how should I structure my respond to achieve my intentions?
It looks like I shouldn’t use the relationship object attributes.
Is this compliant to the spec, how should I indicate that the included documents aren’t a ‘full version’ of them? Is there no need for that?

As far as I recall the specification it doesn’t enforce all fields of a resource if client doesn’t include a fields parameter in it’s request. Fields are attributes as well as relationships. So this should be fine. However it may cause confusion on consumer side as they might not notice that there are additional fields only available if using sparse fieldset or a specific endpoint.

I would recommend to use related resource links for your use case. They don’t put additional workload on your API as they don’t require a database lookup but on the other hand tell the consumer that there is a relationship and how to get it’s data.

2 Likes