Type in attributes


#1

Accordingly to the spec Fields section:

resource can not have an attribute and relationship with the same name, nor can it have an attribute or relationship named type or id.

But what to do if model has type attribute. For example:

{
    "data": {
        "id": "1",
        "type": "score",
        "attributes": {
            "amount": 10,
            "type": "quality"
        }
    }
}

#2

Change the model. If you absolutely must _type is a good way, but from a semantic standpoint there may be a less generalized name to use instead.


#3

Thank you for the reply.

Is there any restrictions to have type key in nested attribute json object?

{
    "data": {
        "id": "1",
        "type": "something",
        "attributes": {
            "blabla": "bloblo",
            "wage": {
                "system": "time-rate",
                "type": "hourly",
                "amount": "50",
            }
        }
    }
}

#4

There isn’t any restrictions in that direction as the type/id restriction is set to establish the reserved word in the key space for the fields. Since that wouldn’t be a key for the field, it wouldn’t pose a problem. However, two things to keep in mind is it wouldn’t be a great idea to nest the field just to be able to get around the rule. The other note I generally like to give is that complex attribute fields are far more often than not resources in their own right and would be better served in your design as related resources.


#5

When I’m facing situation like that I’m always starting to think that it would be better to split everything to relationships because it will be easier to extend app functionality later, it’s cheaper to find related resources by IDs than by string values, but on the other side I’m starting to think that I’m making very complicated API which will be hard to work with.

For example with wage another one approach was to split complex attribute to many relationships.

GET /jobs/1?include=wage.currency,wage.system,wage.unit

{
  "data": {
    "type": "job",
    "id": "1",
    "relationships": {
      "wage": {
        "data": {
          "type": "wage",
          "id": "2"
        }
      }
    }
  },
  "included": [
    {
      "type": "wage",
      "id": "2",
      "attributes": {
        "rate": "300"
      },
      "relationships": {
        "currency": {
          "data": {
            "type": "currency",
            "id": "31"
          },
          "system": {
            "data": {
              "type": "wage-system",
              "id": "4"
            }
          },
          "unit": {
            "data": {
              "type": "wage-unit",
              "id": "5"
            }
          }
        }
      }
    },
    {
      "type": "currency",
      "id": "31",
      "attributes": {
        "code": "USD"
      }
    },
    {
      "type": "wage-system",
      "id": "4",
      "attributes": {
        "name": "time-based"
      }
    },
    {
      "type": "wage-unit",
      "id": "5",
      "attributes": {
        "name": "hourly"
      },
      "relationships": {
        "system": {
          "data": {
            "type": "wage-system",
            "id": "4"
          }
        }
      }
    }
  ]
}

But wouldn’t it be harder for developers to work with it?

With complex attribute it will look like this:

GET /jobs/1

{
  "data": {
    "type": "job",
    "id": "1",
    "attributes": {
      "wage": {
        "system": "time-based",
        "unit": "hourly",
        "currency_code": "USD",
        "rate": 300
      }
    }
  }
}

What questions should developer to ask to clearly understand is it complex attribute or relationships?

Sorry for that off-topic question, but it’s rasing every time I’m trying to find the right word not so common as type: “Maybe it’s better to create one more relationship with it’s own type instead of introducing complex attribute?”.


#6

As a designer you have the freedom to chose the format and the default included relationships. So if you have a natural grouping of relationships 3 or 4 deep which would be beneficial to send as the default, you are in control until the caller includes sparse fieldset or include query parameters.

I have no idea how you got to your first design, it’s redundant, and honestly not good which, you correctly state, offers a poor DX. The later example however, is easily handled in a relationship and I am at a loss to figure out the part you don’t grasp yet.

{
  "data": {
    "type": "job",
    "id": "1",
    "attributes":{
      "name":"First Job"
    },
    "relationships": {
      "compensation": {
        "links":{
          "self":"https://example.org/api/job/1/relationships/compensation",
          "related":"https://example.org/api/wages/5"
        },
        "data":{
          "type":"wage",
          "id":"5"
        }
      }
    },
    "included":[
      {
        "type":"wage",
        "id":"5",
        "attributes":{
          "system": "time-based",
          "unit": "hourly",
          "currency_code": "USD",
          "rate": 300
        }
      }
    ]
  }
}

The question I ask is very simple, does the complex type make sense in it’s own context? Or does it require the context of the parent resource to make sense? Using your wage resource example modified as above, the wage IS a complete concept. However, if you wanted to simply have a resource “rate”, this would require more context in order for it to make sense.

I hope that helps!


#7

Thank you for pointing out idea about default relationships until include query parameters will appear, I’ve never thought about this way. I’ve always thought that include query param should always append to defaults, and not overwrite them.

Accordingly your example, I didn’t understand why there is no data key in your wage relationship and doesn’t have type and id, isn’t it a requirement? And isn’t relationship’s attributes should be placed in included section?

I’ve thought that only this way is appropriate by the specs:

{
  "data": {
    "type": "job",
    "id": "1",
    "attributes":{
      "name":"First Job"
    },
    "relationships": {
      "wage": {
        "data": {
          "type": "wage",
          "id": "2"
        }
      }
    }
  },
  "included": [
    {
      "type": "wage",
      "id": "2",
      "attributes": {
        "system": "time-based",
        "unit": "hourly",
        "currency_code": "USD",
        "rate": 300
      }
    }
  ]
}

#8

I apologize for the confusion, I was rushed trying to post my answer to you before I had to leave work at the end of the day.

You are 100% correct the relationship field should have a resource identifier object, in addition to links for self and hopefully other relevant things too. In my haste I simply moved the wage complex type to demonstrate the concept.

I’ll go back and fix it to reduce any future confusion, but clearly you understand the structure just fine. :smiley:


#9

Nothing wrong. I just was scared that I’ve missed something huge in json:api and was in hope that you will say: “it’s just a concept” :} Thank you for the clarification!


#10

You have asked:

I have no idea how you got to your first design

Thats easy. I’m always asking myself a question, is this entity (relationship) could exist without parent entity and will have zero-level endpoint to get it and all it’s relations?

  • if NO - I’m transforming it to complex attribute.
  • If YES - I’m keeping it as relationship.

For example if wage will be a relationship for the job, then I will have zero-level GET /wage endpoint in my API for sure. I was always thought like that. Such a complicated example was born because each of these entities has zero-level endpoints: /wage-systems, /wage-units, /currencies.

On the other example, if there will be requirement to add job-position to the job I’d prefer to have it as relationship because I will 100% sure there will be /job-position/{id}?include=jobs endpoint which return a list of jobs related to this position.


#11

Yes the devil’s advocate design approach is always there, you could in fact build any API you ever wanted with /resource and /char/[0,1] endpoints. As a computer scientist it’s tempting to think that way, but that road generally leads to premature optimization in a BIG way.

To tackle this problem I often speak of domain value in deciding where to draw the line between a dependent complex attribute and a related resource. From a high level perspective I generally aim to have the semantics of the domain drive my initial pass, and then I look closer for the point where the value of the context becomes more than the sum of the attributes. For example, you could create a /rate resource, but that would mostly just amount to an enumeration of some natural numbers, but add enough pieces together, and a /wage resource now has conceptual value beyond the sum of its parts. You may find my blog on hypermedia API design and other API related posts helpful.

I do highly advise a shallow resource design, or ‘zero-level’ endpoints. A primary reason for this is to maintain the simplest API design possible, and a shallower design is more likely to lead to opportunities for design consolidation. In your example, you aim for higher specificity at the ‘zero-level’ where I would advise generalizations, with semantic distinctions via message structure meaning a generally defined resource, sending a specific type message.

As for your last paragraph, I think you intended something more like this.

/job-position?filter[job]=5

A couple of points on this, jsonapi requires relationship links to be accessible which guarantees a bidirectional reference capability between the two resources through /.../relationships endpoints on the primary resource. The secondary resource has the filter parameter on a query for the resource collection, as I included above. The second point is reinforcing my previous statement about overly specific resources in your ‘zero-level’ designs. Always remember Postel’s Law “be liberal in what you accept, and conservative in what you send”, and using overly specific resource design does the opposite.


#12

I’ve read your blog earlier, it’s really huge work and I’ve learned a lot from it. Especially about content negotiation and versioning.

Not sure that it’s will be exact what I want to do. Maybe because I’ve used wrong word for example. Lets say job-position == profession. And I want to have:

  • List of all Jobs: /jobs
  • Dictionary of all Professions: /professions
  • Detailed info about one Profession: /professions/{professionId}
  • Detailed info about one Profession with jobs which related to it: /professions/{professionId}?include=jobs

So if I’ve understood you right to get jobs filtered for profession it’s better to use:

/jobs?filter[profession]={professionId}

But if I need to get Profession with all it’s relationships I need to use:

/professions/{professionId}?include=jobs,candidates,rel3,rel4

#13

As I understand the example you have provided, this is the same concern to be addressed as the tag and blog post example.

To clarify my understanding, when I read dictionary of all professions I interpreted this as ‘collection’ and not as an enumeration.

As an API designer, the json:api spec gives you the latitude to determine if the implicit target relationship is included in the relationships link collection or not. My previous post described the scenario where you wouldn’t want to include this link, and therefor would need to retrieve the items via a backreference query. Imagine a resource which has relationships to 50 other resources, the response to a query of the relationships collection would become very large if all the relationships were included. If only 10 of those relationships originate from this resource, it would be intuitive to include only those in the collection. As I said, you have the latitude to make these decisions as there is no hard rules around the representations you build.

Given the domain and or semantic need to reference described in your post, and in the similar tag / blog-post examples, it would be a good idea for it to be included in the relationship collection itself. The resulting query to the profession resource would be something like this:

/professions/{profession_id}/relationships/jobs

Data shaping via the ‘include’, ‘fields’, and pagination parameters is really unrelated to the discussion of the reference, which is only concerned about directing to the appropriate resource.

Another point to make clear, I am not advocating path driven semantics in these posts, I am simply using the commonly understood example format from the spec to demonstrate the concepts clearly.

From a learning perspective I think it would be worth your time to go back and read the specification again with a focus on understanding the purpose of the ‘include’ and ‘fields’ query parameters. Much of the power of the json:api format stems from the processing of these parameters. They are very nuanced and you consistently have been showing ‘include’ in your examples when your stated goals are solved by a ‘filter’ operation.

Finally, thank you for the kind words on my work. It was my intent from the beginning to help others understand these topics more clearly, and it’s very rewarding to know it has been helpful.


#14

By Profession Dictionary I meant document with collection of resources:

{
  "data": [
    {
      "type": "profession",
      "id": "1",
      "attributes": {
        "name": "Carpenter",
      }
    },
    {
      "type": "profession",
      "id": "2",
      "attributes": {
        "name": "Tailor"
      }
    }
  ]
}

Thank you so much for all your feedback. I’ve revised all my decisions about API design and clearly see that filter[relationship] parameters will simplify it and will prevent duplicated endpoints, ease code maintaining and decrease amount of tests need to be done.

/jobs?filter[profession]={professionId} just replaced /professions/{professionId]/jobs and now I have only 1 endpoint. Same for another similar cases.


#15

I’m glad I was able to help, good luck!