How to request a Computed Resource Object?


#1

I’m working on an API which requires a way to display a set of remaining values after creating a relationship between two database resources. This resource itself would not be persisted in the database but allows the user to decide if he would like to create the relationship.

Example: A user is considering applying a Pre-Approval to an Expense Report.
This endpoint will return a resource of remaining balances within the Pre-Approval if it were to be applied to the Expense Report’s list of expenses.

Here’s what we settled on:

POST 
{
  data: {
    type: "preapprovalsummaries",
    attributes: {
      expenseReportId: 1,
      preApprovalId: 2
    }
  }
}

RESPONSE
{
  data: {
    type: "preapprovalsummaries",
    id: 1-2,
    attributes: {
      expenseReportId: 1,
      preApprovalId: 2,
      something: "else"
    }
  }
}

Has anyone else dealt with a similar scenario? Does this look good to the community or does someone have a preferred alternative?


#2

Returning a computed, temporary (i.e. non-persisted) resource is easy, and the solution you came up with for generating the id can definitely work.

But I think there’s a more general problem with your example, which is that it’s not taking full advantage of JSON API’s concept of relationships. If you expose an attribute like expenseReportId: 1, that doesn’t give a generic client any clue that you’re actually talking about a pointer to another, distinct resource. For all the client knows, 1 could be a data value, not a relationship (e.g. numberOfChildren: 1).

If you want to communicate explicitly that these fields are relationships (which also means you can communicate instructions for fetching/modifying the related resources), your resource object should instead look something like this:

{
  "type": "preapprovalsummaries",
  "attributes": {},
  "relationships": {
    "report": { 
      "data": {"type": "expense-reports", "id": "1"}
    },
    "preapproval": { 
      "data": {"type": "pre-approvals", "id": "2"}
    }
  }
}

#3

I like the structure of your response. It’s lacking an id though and the spec calls for an id attribute on all resource objects. http://jsonapi.org/format/#document-resource-objects.

Is the best we can do a concatenated id? 1-2? This seems goofy to me.


#4

There are certainly other options, but which one to pick depends on what it is about the "1-2" approach that seems goofy to you. If it’s the presence of the dash (you’d rather have just a single number), you could always take the string "1-2", hash it, and use that. That way, your "id" would still be derived from the ids of the constituent expense and preapproval resources (which might make more sense for client side caching). On the other hand, if you think of each request to compute the the remaining balances as it’s own standalone thing, then you could make the id in the response some independent value (a timestamp, a date string, monotonically increasing numeric values, whatever).

Sidenote: the "id" does need to be a string though, so make sure you use quotes in your final version!


#5

The fact that the id can not be used to point to an existing record in the database the same as other resource objects.


#6

Ideally, an API should not be a direct 1-to-1 mapping of your database. This leaks your internal implementation into the wild and tightly couples clients to things they shouldn’t have to care about.

A resource is a useful representation of a thing you care about, not a record in a database.


#7

I don’t see this specified anywhere on jsonapi.org. Can you point me to the section that states this?

EDIT: I found it here: http://jsonapi.org/format/#document-resource-objects

However, I am wondering. Do all values need to be strings? Can numbers be used as well? In all examples in the docs it appears the numbers are strings. Is this only for ids?


#8

Your data can be whatever it is, but the pre-defined keys have rules. As it says in that section:

Every resource object MUST contain an id member and a type member.
The values of the id and type members MUST be strings.

So those two must be strings.