Metadata About Relationships


#1

I’m having difficulty understanding how JSON-API represents relationship metadata.

Let’s say I have an objective and that objective has different concepts; but those concepts don’t all relate to the objective in the same manner; some are primary, some are secondary and some are tertiary.

So my primary resource, the objective, will have a relationships object that links it to the concepts. Here are the different options as I see them:

Option 1 - different objects for each relationship type:

{
“type”: “objective”,
“id”: “00559422-02E7-11DB-8468-BA18C3C820AE”,
“relationships”: {
“primary_concepts”: {
“data”: { “type”: “concept”, “id”: “02B7” }
},
“secondary_concepts”: {
“data”: { “type”: “concept”, “id”: “3AF6” }
},
“tertiary_concepts”: {
“data”: { “type”: “concept”, “id”: “CB23” }
},
}

Option 2 - different relationship types have different attributes:

{
“type”: “objective”,
“id”: “00559422-02E7-11DB-8468-BA18C3C820AE”,
“relationships”: {
“concepts”: [
{
“data”: { “type”: “concept”, “id”: “02B7” },
“attributes”: { “emphasis”: “primary” }
},
{
“data”: { “type”: “concept”, “id”: “3AF6” },
“attributes”: { “emphasis”: “secondary” }
},
{
“data”: { “type”: “concept”, “id”: “CB23” },
“attributes”: { “emphasis”: “tertiary” }
}
]
}

Option 3 - different relationship types have different meta:

{
“type”: “objective”,
“id”: “00559422-02E7-11DB-8468-BA18C3C820AE”,
“relationships”: {
“concepts”: [
{
“data”: {
“type”: “concept”,
“id”: “02B7”,
“meta”: { “emphasis”: “primary” }
}
},
{
“data”: {
“type”: “concept”,
“id”: “3AF6”,
“meta”: { “emphasis”: “secondary” }
}
},
{
“data”: {
“type”: “concept”,
“id”: “CB23”,
“meta”: { “emphasis”: “tertiary” }
}
},
]
}

I know Option 1 fits within the spec, but I would consider it bad design. Ideally, I’d go with Option 2, but is that allowed? What about Option 3, is that a compromise? Is there a 4th option I’m not considering?


Pivot data for many-to-many relations
How to handle a composite ID in a many-to-many?
META in sparse fieldsets
#2

First, a small point: in all of these examples, it seems like the data member should hold an array, since you want the relationship(s) to be to-many.

Option 2 is not conformant, because the data member in a relationship holds resource identifier objects, not full resources.

A variant of option 3 would be allowed, as follows:

{
  "type": "objective",
  "id": "00559422-02E7-11DB-8468-BA18C3C820AE",
  "relationships": {
    "concepts": {
      "data": [
        { "type": "concept", "id": "02B7", "meta": { "emphasis": "primary" } },
        { "type": "concept", "id": "3AF6", "meta": { "emphasis": "secondary" } },
        { "type": "concept", "id": "CB23", "meta": { "emphasis": "tertiary" } }
      ]
    }
  }
}

And, as you suggested, option 1 is also allowed (with data as an array).

I’d suggest option 1 because, a generic client can’t make sense of the data in meta…which isn’t what you want, since the emphasis seems like a big part of your data model.

What is it about option 1 that strikes you as bad design? After you go into that, we may be able to find another solution that avoids those issues.

(And there are other solutions…e.g., you could create an intermediate resource type, something like lesson-objectives, that combines an emphasis level and a relationship to a concept in a single resource—though that has it’s own downsides.)


#3

You asked what it is about option 1 that we considered bad design. A part of the reason we are considering using JSON API is because it offers us an abstract design to house all our data in. By forcing us to take what is essentially one resource, the concept, and turn it into 3 manufactured resources (primary_concepts, secondary_concepts, and tertiary_concepts) it feels like we are being less abstract, doing a work-around because we don’t just want relationships among resources but weighted relationships.

What if instead of primary, secondary, tertiary we instead decide to just weight the relationships with an value between 1-100. I’m not going to create 100 different resources (e.g. weight_1_concept, weight_2_concept, weight_50_concept, weight_100_concept).

What if we decide each relationship has more than one piece of metadata associated with the relationship, something like an emphasis (primary, secondary, tertiary) and a point of origin (automated, manual). We don’t want to need to create a Cartesian product (e.g., automated_primary_concepts, manual_primary_concepts, automated_secondary_concepts, etc).

Is adding a meta object really our only option here? We can’t be the only people wanting to qualify relationships are we?


#4

You’re definitely not the first person who’s asked for this, and it’s something that was considered before 1.0 (See #431 and the preceding discussion in #415.)

Ultimately, the editors at the time felt that supporting attributes in relationship objects (and therefore turning relationships into more than just named pointers) wasn’t worth the complexity. Or, at least, wasn’t worth it for 1.0; there’s a small chance this will be added to the base spec in the future, and, almost certainly, the still-in-development extension system will make it possible to support this case.

The ability for relationship objects to have a "meta" field was a compromise, and one that can be useful for when generic clients don’t need to understand the relationship’s attributes. But the recommended, generic solution is to create a new resource type, as I alluded to above. So, for your case, that would be creating a objective-concept type whose resources look something like this:

{
  "type": "objective-concept",
  "id": "1",
  "attributes": {
    "emphasis": "primary", 
    "origin": "automated"
  },
  "relationships": {
    "concept": {"type": "concepts", "id": "CB23"}
  }
}

Then, your objective resources simply relate to these objective-concept resources like so:

{
  "type": "objective",
  "id": "00559422-02E7-11DB-8468-BA18C3C820AE",
  "relationships": {
    "concepts": [{"type": "objective-concept", "id": "1"}, //...]
  }
}

Does that help?

P.S. The json api convention is to use plural type names, to make collection uris (like /posts) match the type so, in the above example, more conventional type names would be objectives and objective-concepts.


#5

Can you please comment on how you would create and update relationships with associated metadata?

Also, this feels dirty. The data with these relationships defines them - the relationship information is not complete without the “metadata”. So putting them in a generic bag (and having to both serialize and deserialize them to/from there) is very awkward.