Same resource different attributes, Polymorphism

Hi everyone,

I am new in this community but I have been using JSON API for a while. Though now this particular problem kinda got me wondering if I am doing the right thing.

So let say I have a Transportation resource on /transportations, a Transportation might have various kind of types, eg. car, ship, plane and so on. Each type of Transportation might have different attributes, for example plane has wheelsNum and wingTypes while ship has sailsNum and helmRadius while they also share some common attributes such as maxPassenger, fuelType and so on. How do we model this kind of resource?

I can imagine having this when hitting GET /transportations:

{
.....
  "data": [
    {
      "id":"1",
      "type":"transportation",
      "attributes":{
        "transportationType":"ship",
        "sailsNum":4,
        "helmRadius":20,
        "maxPassengers":15,
        .....other attributes.....,
      }
    },
    {
      "id":"2",
      "type":"transportation",
      "attributes":{
        "transportationType":"plane",
        "wheelsNum":7,
        "wingTypes":"....",
        "maxPassengers":50,
        .....other attributes.....,
      }
    }
  ]
.....
}

But that means same resource might have different attribute fields. Though I do not see it anywhere in the spec that it is prohibited (or maybe I missed it), I do not feel right this is the right thing to do just because it looks inconsistent.
Is there a better approach? Or that one is just enough?

Thanks

Is there any reason not to have several types of transport, each with their own type value, and whatever appropriate fields are useful?

-Fred

Hi @fdrake , thank you for responding to my question.

A couple of reasons not to have several / different transport type:

  • The client side would need to show all the transports available in the system in one page
  • The number of types might grow a lot more in the future, the example I gave you was just a simplification (with different analogy) of the real problem, in the same context above it might have bike, horse, skateboard, zeppelin, air_balloon, … so many more
  • Having the client to handle each different type of transport endpoints would mean they having a risk of missing on a new type whenever a new type is being published (less of a concern though)

That’s up to the client, though, isn’t it? You could offer filtering by type, or whatever sort of filters make sense.

Whether you use the JSON:API type or some internal kind field, that issue doesn’t change. To deal with fields that are not common across all types of transports, the client will need to understand them. type is nice because it’s already intended for that purpose, and any client encountering a type they don’t know about should be able to recognize it as something unknown (though they may not recognize it as a transport).

A dynamically expanding set of types (whether type or an internal kind field) can be handled by also making available a collection of type descriptions, which can take the form of schema, that can be used to inform about displayable properties and what properties can be set on creation or update, if those are concerns for your application.

Ultimately, for a complex and expanding set of types, you need to be able to deal with out of date clients, or predict the ways in which your application is most likely to expand.

-Fred

That is true, even though the client is actually just another internal team but it is doable. All these are negotiable as long we stick to the standard.

Another thing to confirm though, since the client still needs to show all the transports within one page anyway, meaning they need to call multiple endpoints (which is fine), how would they implement filtering in that case? I cannot imagine sorting on the client side would be wise, especially when the data is huge

Agree, eventually they need to handle all the fields differently in the “details” / “view” page of the transport

Not quite sure what you mean here. Can you please point me to a link / article of this kind of pattern?

Thanks.

I don’t see a need for separate endpoints. Your initial question presented a GET /transportations endpoint that returned a collection of transportation resources. Having many types of transport types doesn’t need to change that. It becomes a collection that can contain many different kinds of transports.

It can be easy to overload the simplicity of the example URL structures in the specification with meaning, but no such constraints are present. Constraints are only attached to URLs returned by the server in particular elements of the response.

If you’re using the filter query parameter family (to use terminology from the proposed version 1.1 of the specification), a request to retrieve a single transport type might be something like this:

GET /transportations?filter[type]=donkey

Selecting multiple types might then look something like this:

GET /transportations?filter[type]=donkey,plane,starship

That should combine nicely with any other filters and sorting parameters.

When I say “type descriptions”, what I mean is some sort of schema that can inform the client of the fields available for each concrete type, what the values can be, when the can be set (creation POST, update PATCH, etc.), whether they can be used to sort / filter, whatever is needed. I’m sure there are examples in the literature, and I’ve worked with such things tailored for form-building in the past, so it’s not a fresh idea. I don’t have a handy stash of links to articles on the topic, though; sorry!

-Fred

1 Like

Ah, this is new to me. I did not know that one endpoint (eg. /transportations) may contain multiple type of resources. So I imagine /transportations will be something like the following (even with the same id as long the type is different)?

{
.....
  "data": [
    {
      "id":"1",
      "type":"transportation-ship",
      "attributes": {
         "sailsNum":4,
         "maxPassengers":15,
          ......other attributes......
      }
    },
    {
      "id":"1",
      "type":"transportation-plane",
      "attributes": {
          "wheelsNum":7,
          "maxPassengers":50,
          ......other attributes......
      }
    }
  ]
.....
}

Whoops, my bad. I meant to write sorting not filtering. Since sorting across different endpoints might be impossible if I have to go with the multiple endpoints implementation. But don’t mind this.

It is clear now, this is exactly what I am looking for. So in summary JSON API supports multiple type within the same endpoints as long the same type value has the same attributes. Love JSON API even more now.
Thanks man, this is valuable, appreciate it.

1 Like

I’m glad my comments have been helpful!

Since resource identity is based on both type and id, that can be acceptable, but you do want to think about how you’d address a specific transportation resource.

I find it helpful to think of two kinds of collections: those that “own” the resources contained, and those that only have references to resources elsewhere (think of to-many relationships). Using URL paths like /type/id is certainly convenient, but works best when the id values are unique within /type/. And JSON:API does not impose any requirement that positive, sequential integers are used for id values for each type.

How best to implement that depends very much on your persistent store and other software you’re using to build your application, of course.

-Fred

You are right that it is not explicitly forbidden. But it seems to go against the intend of the specification:

§ 5.2.1 Identification

[…]

The type member is used to describe resource objects that share common attributes and relationships.

JSON:API — Latest Specification (v1.1)

Just wanted to cite the specification itself additional to the great explanation by @fdrake.

1 Like