Working with data on relationships


#2

Why do you have a resource for a Skill? What attributes / relationships does that resource have (apart from a level and a link to a specific Character)?


#3

A name, a short description, some help text, an icon, requirements for reaching certain skill levels (e.g. to reach level 20 swords you need to have 10 strength).

And other things might link to a skill for various reasons - e.g. a short sword can be used with any swords skill level, but a great sword needs swords level 15 to be useable.


#4

The problem is that your “id” is not an id. Identifier must be unique, so any object of “skills” with id=“swords” should be the same. In your example id should be kinda “swords-20”, “bows-3”.

There is another way: now you’re trying to treat “id” as a subtype which is wrong, but you can use another type.

          "relationships": {
            "skills": [{
              "links": {},
              "data": {
                "type": "skill-swords",
                "id": "20"
              }
            }

But it makes a little sense if all your skills have the same attributes.


#5

So am I right in understanding that the ID here is the ID of the Relationship, not the ID of the object the relationship is for?

Two characters will have the same “swords” skill. It means the same thing. It is the same thing. And two different swords will refer to this exact same skill as well. As such, I’ve always assumed that the relationship to this skill would have the ID of the skill it relates to, as opposed to some arbitrary made-up ID. (Note - I’m not using a SQL database, so I don’t have linking tables with arbitrary generated primary keys)

If I’m understanding what you’ve just said correctly, I would expect that two different characters and two different swords that all link to the same skill will have different IDs in the Relationship object, which then makes it difficult to relate in the client that these actually are the same thing.

Literally what I’m wanting to model here is:

Bob-------------------    -----------------Great Sword
             (20)    |    |    (15)
                     Swords
             (5)     |    |    (1)
Fred------------------    -----------------Short Sword

#6

No, id of the object.

So this confirms my assumption that you think of “id” here as a subtype.

Let’s take a hypothetic full example of your model just to be clear.

{
  "data": [{
    "type": "characters",
    "id": "1",
    "attributes": {},
    "relationships": {
      "skills": {
        "data": [{ 
          "type": "skills", "id": "swords-20" 
        }, { 
          "type": "skills", "id": "bows-3"
        }]
      }
    }
  }],
  "included": [{
    "type": "skills",
    "id": "swords-20",
    "attributes": {
      "weapon": "sword",
      "level": 20,
      "desc": "Great skill, man!",
      "icon": "sword-big.png"
    }
  }, {
    "type": "skills",
    "id": "bows-3",
    "attributes": {
      "weapon": "bow",
      "level": 3,
      "desc": "Try harder!",
      "icon": "bow-small.png"
    },
  }]
}

Here is how I imagine your model. Now try to describe your vision and let’s see where is misunderstanding.


#7

So you’d expect there to be 20 different resources for swords, and 20 for bows, and 20 for … Except that all 20 of the Swords resources will be virtually identical, as will all 20 of the Bows skills. That seems redundant.

In the backend modelling, the skill level is literally the metadata stored on the relationship between the Character and the Skill. If you know Neo4J Cypher then its:

CREATE (bob:Character {name: 'Bob'}), 
       (fred:Character {name: 'Fred'}),
       (swords:Skill {name: 'Swords'}), 
       (bob)-[:SKILL_LEVEL {level: 20}]->(swords), 
       (fred)-[:SKILL_LEVEL {level: 3}]->(swords)

Now, I’m OK with the idea that the Skill Level portion could be another embedded entity, which I can model in JSON API as:

{
  "data": [{
    "type": "characters",
    "id": "1",
    "attributes": {},
    "relationships": {
      "skills": {
        "data": [{ 
          "type": "skill-levels", "id": "swords-20" 
        }]
      }
    }
  }],
  "included": [{
    "type": "skill-levels",
    "id": "swords-20",
    "attributes": {
      "level": 20
    },
    "relationships": {
        "skill": {
            data: {"type": "skills", "id": "swords"}
        }
    }
  }, {
    "type": "skills",
    "id": "swords",
    "attributes": {
        "name": "Swordsmanship",
        "icon": "swords.png"
    }
  }]
}

And for retrieving data, that works fine - if being a bit more verbose. But there’s no way that I can see from the spec to create and update this data in one go. If a character has 10 skills, then to create the character and set his initial skill levels would involve 11 HTTP Request, instead of just 1.


#8

Can you show the attributes of, say, “swords” skill level 1 and “swords” skill level 20? Are they the same? If yes — what’s the difference? If no — how do you know which attributes to use for the each level of the skill?

You DB schema should not affect your API model. What if you’ll disappointed with Neo4J and want to change it? Will you redesign all your API?


#9

Can you show the attributes of, say, “swords” skill level 1 and “swords” skill level 20? Are they the same? If yes — what’s the difference? If no — how do you know which attributes to use for the each level of the skill?

They’re identical. The attributes would be something like:

{
    "name": "Swords",
    "description": "Fighting with swords",
    "help": "This skill influences the ability to fight using a sword, both in terms of the ability to hit your opponent and to block any incoming attacks from them.",
    "icon": "http://localhost:8080/icons/skills/swords.png",
}

Then there would be a similar one for Bows, Axes and so on.

When you say “What’s the difference?” are you suggesting that the API could have fabricated resources for the skill at different levels, with an added attribute of the level itself? That seems like a very clunky, hacky workaround to the problem, and one that will likely cause more problems - for example, needing to have all sorts of added validation that there is exactly one of each type of skill when the resources are no longer unique. It does make it possible to create/edit the character and set it’s skills in one go, but not in a very elegant way. I’d have to perform a query of all of the skills resources for “swords”, find the one that has the level that I want, and replace the existing relationship with that one. When what I actually want to do is change the level attribute on the relationship link itself.

You DB schema should not affect your API model. What if you’ll disappointed with Neo4J and want to change it? Will you redesign all your API?

Absolutely it shouldn’t. That was purely an example in something that was not JSON API to try to express what I’m trying to achieve better. Especially since Neo4J/Cypher do support the whole properties-on-relationships that I’m trying to express. You can do exactly the same in SQL - using a link table with extra columns, or MongoDB, or just about any database engine.


#10

Ah, now I see. What you call “skill” is just a “skill description”, the content doesn’t depend on level.

Ok, let’s try to change a bit.

{
  "data": [{
    "type": "characters",
    "id": "1",
    "attributes": {},
    "relationships": {
      "skills": {
        "data": [{ 
          "type": "skills", "id": "swords" 
        }, { 
          "type": "skills", "id": "bows"
        }]
      }
    }
  }],
  "included": [{
    "type": "skills",
    "id": "swords",
    "attributes": {
      "level": 20
    },
    "relationships": {
      "description": {
        "data": {"type": "skills-description", "id": "swords" }
      }
    }
  }, {
    "type": "skills",
    "id": "bows",
    "attributes": {
      "level": 3
    },
    "relationships": {
      "description": {
        "data": {"type": "skills-description", "id": "bows" }
      }
    }
  }]
}

Or if it’s important to get all the data in one response…

{
  "data": [{
    "type": "characters",
    "id": "1",
    "attributes": {},
    "relationships": {
      "skills": {
        "data": [{ 
          "type": "skills", "id": "swords" 
        }, { 
          "type": "skills", "id": "bows"
        }]
      },
      "descriptions": {
         "data": [{ 
          "type": "skill-descriptions", "id": "swords" 
        }, { 
          "type": "skill-descriptions", "id": "bows"
        }]
      },
      }
    }
  }],
  "included": [{
    "type": "skills",
    "id": "swords",
    "attributes": {
      "level": 20
    }
  }, {
    "type": "skills",
    "id": "bows",
    "attributes": {
      "level": 3
    }
  },{
    "type": "skill-descriptions",
    "id": "swords",
    "attributes": {
      "name": "Swords",
      "description": "Fighting with swords",
      "help": "This skill influences the ability to fight using a sword, both in terms of the ability to hit your opponent and to block any incoming attacks from them.",
      "icon": "http://localhost:8080/icons/skills/swords.png"
    }
  },{ ...
  }]
}

A bit less natural, but still.

(ps: I personally don’t like when API returns this kind of resources, like texts, icons, titles — all theese things can be stored on a client side and updated in some other way. But it’s always up to you)


#11

And that works great - for retrieval. It doesn’t work well for create/update is the problem I’ve got. Unless I’m missing something - in which case please please tell me what :slight_smile: - it’s not possible to create/edit a characters resource and all of the skills entries in a single request. The spec states “The request MUST include a single resource object as primary data.”, and I can’t see anything anywhere that would let me create all of the sub-resources at the same time. (Except for the slight cop-out of the request for creating a character including the skill levels in the attributes or the meta, but that then doesn’t feel right because the create and the retrieve are different)

(ps: I personally don’t like when API returns this kind of resources, like texts, icons, titles — all theese things can be stored on a client side and updated in some other way. But it’s always up to you)

It’s going to be a meta engine - so not a game itself, but a system that people can make games on. As such, the actual lists of skills and so on are not always going to be the same, so they have to be provided by the server instead.


#12

And this could be a good sign that your “skills” are not relationships but attributes.

Usually when you want to create a resource with relationships you expect those resources exist. In your case it’s impossible, because you want to have “local scoped ids” - in other words, two objects with the same id=“swords” can be globally different. And that’s another sign that this is actually attributes :slight_smile:.

If you strongly want to have skills separated from character there is another option: to have “skills” object with attributes “swords”, “bows” and others.

{
  "data": [{
    "type": "characters",
    "id": "1",
    "attributes": {},
    "relationships": {
      "skills": {
        "data": [{ 
          "type": "skills", "id": "1" 
        }]
      }
    }
  }],
  "included": [{
    "type": "skills",
    "id": "1",
    "attributes": {
      "swords": 20,
      "bows": 3
    }
  }]
}

(and yes, you still can not create all the character at once, but - read this comment from the beginning :slight_smile: )

Optionally there could be a “descriptions” relationship, but I’d recommend to treat it as a static or meta data (client should preload this kind of data and update it with other flow).


#13

This is feeling like we’re going in circles now.

What I want to have is:

  • Pre-existing resources - which I’ve called skills and you’ve called skill-descriptions. These will be created in the system up-front and only referred to from this point on.
  • Characters
  • Links between Characters and Skills, where somehow the link dictates that the character has a certain level in a skill

As such, the fact that the “skill-descriptions” resource already exists makes it a relationship from the “character” in my mind. Any time a skill with the id=“swords” is referred to, it’s the exact same resource that is meant, across the entire system. I don’t want “local scoped IDs” at all. I want to have two different pieces of globally unique data, with a link between them, and a piece of data that belongs to that link and not to either end of it.

Yes, arguably the skill levels on a character are actually attributes, but the skill descriptions want to exist separately, and short of having duplication of data in having both attributes defining the skill levels and relationships defining the skill descriptions, I can’t see a way to do it with the spec as it currently stands. You’d end up with:

{
    "type": "characters",
    "id": "bob",
    "attributes": {
        "skills": {
            "swords": 20,
            "bows": 3
        }
    },
    "relationships": {
        "skills": {
            "data": [
                {"type": "skills", "id": "swords"},
                {"type": "skills", "id": "bows"}
            ]
        }
    },
    "included": [{
        "type": "skills",
        "id": "swords",
        "attributes": {}
    }, {
        "type": "skills",
        "id": "bows",
        "attributes": {}
    }]
}

And that works, but the repetition in there is just not good.

(btw - how do you get the code to have colours?)


#14

I still think that “skill description” is a static data, so it should be treated as static, API is generally for dynamic (dependent on request params) data.

I try to find an analogy so that you can understand my idea. Imagine some html page and a css for it. What you want (from my point of view) is to embed full css code (full description of skills) in the html page instead of using links to it (using attributes as ids - “swords”, “bows”, etc). Or embed all styles in tags instead of using css classes.

Let me roughly describe how I think client should work.

  • client is loading
    • check if resources (static data) have updates: download a file with version of static files, compare to its own
    • update if needed (download from your static servers)
  • client is working
    • request a character info in some case
    • look at ids of skills that character has and load from local resources skills’ desciptions for those ids

(btw - how do you get the code to have colours?)

It’s a standard markdown three-ticks block.

```
{"code": "here"}
``` 
{"code": "here"}

#15

@Sazzer I completely understand what you mean here, I have ran into the same problem multiple times.

Another example

Using the articles example used by the specification, imagine there could be multiple authors for an article with one being the main one.

Potential solutions

Option 1: create a new entity type as you mentioned above which has an isMain attribute, an author relationship, and an article relationship. Like you said, this is painful when creating an article, updating stuff, these new entities should be GETtable but they’re pretty useless, etc.

Option 2: add an attribute like mainContactId. Downsides include having to keep this in sync with authors relationship, maybe even having to update this ID to another author if the main author is deleted,
possibly disallowing updating the authors relationship via articles/1/relationships/author, etc.

Option 3: have a mainAuthor as well as an authors relationship (which includes the main one). Similar to option 2 but seems to be more like the JSON API way. It has similar downsides in keeping them in-sync, etc.

Non-option 4: just add attributes to each author resource identifier object :confused:

I have gone for Option 3 in some cases and Option 2 in others, depending on what it represents and how they’ll be used. Whatever is least painful. Note: this is a simple example. In Option 3, I’ve mainContactId. In one of my cases, I’ve had to embed an array of objects here (which among other things contains an ID of another entity).

@Sazzer’s example

I guess the case I just referred to last is most like your problem, @Sazzer. This probably depends on what calls you use most, etc. but I think this is the best option:

{
    "data": {
        "type": "characters",
        "id": "1234",
        "attributes": {
            "skillDetails": [{
                "id": "2",
                "level": 20
            }, {
                "id": "23",
                "level": 3   
            }]
        },
        "relationships": {
            "skills": [{
              "data": {
                "type": "skills",
                "id": "2",
              }
            }, {
              "data": {
                "type": "skills",
                "id": "23",
              }
            }]
        }
    }
}

I’d love if someone could please clear this up :pray:


#16

I had forgotten about meta (thanks @lucas_hosseini on Twitter). This would be another option:

{
    "data": {
        "type": "characters",
        "id": "1234",
        "attributes": {},
        "relationships": {
            "skills": [{
              "data": {
                "type": "skills",
                "id": "2",
              },
              "meta": {
                "level": 20
              }
            }, {
              "data": {
                "type": "skills",
                "id": "23",
              },
              "meta": {
                "level": 3
              }
            }]
        }
    }
}

Note: I’ve used the meta in each resource object identifier but you can also have a relationships.skills.meta.


#17

Many thanks for sharing this very diverse opinion post where each expert has no doubt shared his best knowledge on the topic.
photoshop online cool math games


#18

I do not really understand how this should be implemented. The level meta attributes aren’t actually part of the resource right? How do you determine the level for a skill?

I am using a serialisation module which only allows to create meta attributes based on attributes of the actual data in the resource. So level must be present somewhere in the resource, right?

Consider this similar case:

{
  "study_programs": [
    {
      "program": "some-id",
      "preferred_semesters": [1, 4, 3, 6]
    }
  ]
}

Now this could look like this in JSONAPI format:

{
  "relationship": {
    "study_programs": {
      "data": [
        {
          "id": "some-id",
          "type": "programs"
        }
      ],
      "meta": {
        "preferred_semesters": [1, 4, 3, 6]
      }
    }
  }
}

No serialiser can handle this type though. As I see it meta can not be used for something like this?

So for this case I am guessing it would be best to handle the situation as you mentjioned in your post above the meta post.

Like this:

{
  "attributes": {
    "study_program_details": [
      {
        "program": "some-id",
        "preferred_semesters": [1, 4, 3, 6]
      }
    ]
  },
  "relationship": {
    "study_programs": {
      "data": [
        {
          "id": "some-id",
          "type": "programs"
        }
      ]
    }
  }
}

This seems very unnecessarily hard to update though. I understand there is not proper solution for these cases in the spec, but we have encountered them many times yesterday and today while modelling our database.
Is there really no better solution?


#19

@Sazzer - We have a very similar challenge. Our pragmatic solution has been to use the meta property for the relationship if the additional data to be carried with it is simple (e.g. a single numerical value). We’ve even stretched this a bit further to the point of having a handful of values in meta. I think that goes against the intent of the JSON API spec but is a pragmatic solution. However, when relationships get very complex, we break them out so they exist as their own independent endpoints that have relationships to the other entities as well as their own attributes.

Simplifying the example, here’s the gist of what it would look like as a simple property stored in the meta object:

{
  "data": {
    "type": "characters",
    "id": "1234",
    "attributes": {}
    "relationships": {
      "skills": {
        "data": [
          {
            "type": "skills",
            "id": "swords",
            "meta": {
              "level": 15
            }
          },
          {
            "type": "skills",
            "id": "bows",
            "meta": {
              "level": 20
            }
          }
        ]
      }
    }
  }
}

When things get complex, we make the relationship it’s own entity. E.g. assume the skill had not-only a level but an acheivement date, some details around the mission where they earned the skill, etc. Then we’d model it more like this:

{
  "data": {
    "type": "characters",
    "id": "1234",
    "attributes": {}
    "relationships": {
      "character-skills": {
        "data": [
          {
            "type": "character-skills",
            "id": "a6002dd1-d71f-481c-9291-9df338f77f72"
          },
          {
            "type": "character-skills",
            "id": "63c905c2-7288-4f0e-be1d-520b8727c8c7"
          }
        ]
      }
    }
  }
  "included": [
    {
      "type": "character-skills",
      "id": "a6002dd1-d71f-481c-9291-9df338f77f72",
      "attributes": {
        "level": 15,
        "achieved": "2017-12-14",
        "mission": {
          "title": "Save the Princess",
          "duration": 15376,
          "battles": 12
        }
      },
      "relationships": {
        "skill": {
          "data": {
            "type": "skills",
            "id": "swords"
          }
        },
        "owner": {
          "data": {
            "type": "characters",
            "id": "1234"
          }
        }
      }
    },
    ... etc.
  ]
}

You are right that you can’t create a character and their skills this way in one fell swoop. That’s why we bend the rules for simple stuff and use meta. For complex relationships, we find the better representation outweighs the hassle of multiple calls.

I hope this helps.


How to deal best with relationship in contained list item
#20

We did the same as in your second example. It feels to be most in line with the spec. That said, the spec feels to restrictive for things like data on relationships. Hope there will be some changes simplifying this in the future.


#21

That is intentional. You either have a resource to contain the relationship data, or a very, very limited data flavoring of the relationship. Pragmatically speaking, I understand the reason for the limited data on the relationship, but simply including this option is a slippery slope, as demonstrated by the frequency this topic comes up.

If you need more than a very simple augmentation, the problem isn’t in the spec but in the design of your resources themselves.