"Included" resources with sequence


#1

Hello,

I’d like some advice on how to structure a payload.

We are modelling a delivery route. The route has stops and each stop has parcels. The stops do not exist as separate resources; they are merely items in an array within the route and do not have IDs. The order of the stops is of course very important. The parcels, however, do exist as separately-accessible resources and we want to carry some of the parcels’ data in the payload - so we’re going to use the “included” section of JSONAPI.

As I understand it, I shouldn’t just insert the parcels’ data into the main “attributes” section of the route (although it is tempting). Does this mean that I need to include a reference in the “metadata” of the items within the “included” section to relate them back to the stop?

This would lead to a payload like the following (two stops, three parcels - one parcel for the first stop and two at the second)…

{
  "data": {
    "type": "route",
    "id": 123,
    "attributes": {
      "stops": [
        {
          "stopId": 1,
          "address": "SNIP",
          "instructions": "Leave by back door"
        },
        {
          "stopId": 2,
          "address": "SNIP"
        }
      ]
    }
  },
  "included": [
    {
      "type": "parcel",
      "id": "AB123",
      "attributes": {
        "weight": "10Kg",
        "highValue": false
      },
      "meta": {
        "stopId": 1
      }
    },
    {
      "type": "parcel",
      "id": "CD456",
      "attributes": {
        "weight": "5Kg",
        "highValue": true
      },
      "meta": {
        "stopId": 2
      }
    },
    {
      "type": "parcel",
      "id": "EF789",
      "attributes": {
        "weight": "15Kg",
        "highValue": false
      },
      "meta": {
        "stopId": 2
      }
    }
  ]
}

It doesn’t look quite right to me. Thoughts welcomed.

J.


How to remove a value from an array attribute
#2

There are a lot of design constraints assumed in your introductory information. Why don’t stops exist as resources, or why can’t they? To me this immediately looks like a modeling problem, and with your words I believe you agree. It’s hard to offer solutions to your issue when you come to the table with x, y, z constraints which partially or completely create the issue.

I’m curious what about this API is causing it’s design to be so concrete in such an early stage of your development.

Your hunch is right, that doesn’t look right. Because you basically have reconstructed the ‘relationships’ section of the payload within META and include, caused by your assertion that stops are not resources. There’s a variety of ways you could alter the design of your resources in order to appropriately deal with this, but your solution would also break completely if a consumer provided an includes parameter at execution.

A more appropriate design, which would lend itself to simple serialization, even without the use of hypermedia would be a routes resource which has related stops resources which have related parcels. You must represent your resources in this way in order to utilize {json:api}.

In this case you don’t need to send all data of all resources, you’re free to massage the representations in the default include scenario. Take a look at this example, but keep in mind I’ve trimmed the required link sections from the relationships for clarity and brevity. I’ve also done it by hand, so apologies if I messed up the format slightly in any way.

{
  "data": {
    "type": "route",
    "id": 123,
    "attributes": {
      "status":"PENDING"
    }
  },
  "relationships": {
    "stops": {
      "data": [
        {
          "type": "stop",
          "id": "stopid1"
        },
        {
          "type": "stop",
          "id": "stopid2"
        }
      ]
    }
  },
  "included": [
    {
      "type": "parcel",
      "id": "AB123",
      "attributes": {
        "weight": "10Kg",
        "highValue": false
      },
      "relationships": {
        "stop": {
          "data": {
            "type": "stop",
            "id": "stopid1"
          }
        }
      }
    },
    {
      "type": "parcel",
      "id": "CD456",
      "attributes": {
        "weight": "5Kg",
        "highValue": true
      },
      "relationships": {
        "stop": {
          "data": {
            "type": "stop",
            "id": "stopid2"
          }
        }
      }
    },
    {
      "type": "parcel",
      "id": "EF789",
      "attributes": {
        "weight": "15Kg",
        "highValue": false
      },
      "relationships": {
        "stop": {
          "data": {
            "type": "stop",
            "id": "stopid2"
          }
        }
      }
    },
    {
      "type": "stop",
      "id": "stopid1",
      "attributes": {
        "sequenceId": 1,
        "address": "SNIP",
        "instructions": "Leave by back door"
      },
      "relationships": {
        "parcel": {
          "data": {
            "type": "parcel",
            "id": "AB123"
          }
        }
      }
    },
    {
      "type": "stop",
      "id": "stopid2",
      "attributes": {
        "sequenceId": 2,
        "address": "SNIP"
      },
      "relationships": {
        "parcel": {
          "data": [
            {
              "type": "parcel",
              "id": "CD456"
            },
            {
              "type": "parcel",
              "id": "EF789"
            }
          ]
        }
      }
    }
  ]
}

#3

Many thanks. Your example payload is really clear. Quick clarification, is it necessary to represent both sides of a relationship in one payload? i.e. in an included parcel you’re showing the relationship to the stop, and in an included stop you’re showing the relationship to the parcel. It seems a little verbose.

Our constraint around stops not being resources is because we are trying to implement a JSONAPI interface on top of a legacy system. I can see how much easier it would be if stops were resources, so I’m going to see if we can agree on some sort of identifier for them.


#4

Legacy always causes problems, but it seems like you see how them not being resources really mucks up the whole effort. Shot in the dark, if you really can’t have them be full resources internally, the ID could be something along the lines of a hash of the routeId + stop sequence + a constant padding salt. That should give unique ID’s, or at least unique enough for this use. Not great, but a legacy constraint is usually a pretty solid one.

The inclusion of both relationships is verbose, but I included it to show the implicit bi-directionality of the relationships.

As the developer, you’re free to decide which relationships on a resource are significant enough to warrant inclusion in the /relationships/… link set, which means you also can decide which ones you would include a relationships section within. Massage the contents as you need, but be wary of premature optimization of the payload. This data could be highly cacheable by the client, including those links may be verbose in this narrow scope, but you never know when your consumer might have a different piece of information in your chain available to them, and you wouldn’t necessarily want to force them to search the APIs again for the route which includes stopId = xxxx.

It can get overly verbose certainly, but until you have hard evidence the verbosity is detrimental, you never know when extra relationship information like this can be helpful.


#5

Thank you. Really helpful and much appreciated.


#6

I’m glad I was able to help!