7

I am trying to figure out what's the best approach when dealing with object references in a JSON to be sent to my server for deserialization.

To clarify, what I mean is how to refer to data contained in the JSON body itself.

For the example let's use the following (incorrect) JSON body:

{
  "players": [
    { "name": "Player 1" },
    { "name": "Player 2" },
    { "name": "Player 3" },
    { "name": "Player 4" }
  ],
  "teams": [
    {
      "name": "Team 1",
      "players": [ player1, player2 ],
    },
    {
      "name": "Team 2",
      "players": [ player3, player4 ],
    },
  ]
}

I need some means to refer to those playerX, which would represent the players sent in the players list (Player 1, Player 2, etc...), as you can imagine.

I can think of three different ways of doing this and none satisfy me:

The first way is to simply refer to the player as its position in the players list, this way no extra information is needed in the JSON body. For example, if we had "teams": [ [ 0, 1 ], [ 2, 3 ] ] that would mean we have two teams, the first one composed by P1 and P2 and the second one by P3 and P4.

The other two ways would both use IDs (I would need to implement an ID system for my players, since so far I haven't been in the need of IDs). However, since the players don't exist in the system yet, their IDs are unknown. As I see, we can do two things:

We can explicitly send the player IDs to the server, but we would have to develop ways of preserving ID uniqueness. I would prefer if these were not handled by the user, to by honest, as well as autogenerated. The way we would represent the players in the teams list would look the same like the first choice in format, but instead of the positions, we would have the IDS:

{
  "players": [
    { "id": 1, "name": "Player 1" },
    { "id": 2, "name": "Player 2" },
    { "id": 3, "name": "Player 3" },
    { "id": 4, "name": "Player 4" }
  ],
  "teams": [
      [ 1, 2 ], [ 3, 4 ] 
  ]
}

The second option is safer in terms of ID generation, but it requires two steps. First we send the player data to the server, then the user can ask the server for the players information and check their IDs. This way they create the teams safely because the ID was generated by the server.

Step 1. Send players.

{
  "players": [
    { "name": "Player 1" },
    { "name": "Player 2" },
    { "name": "Player 3" },
    { "name": "Player 4" }
  ]
}

Step 2. Query players. GET /players

{
  "players": [
    { "id": 11, "name": "Player 1" },
    { "id": 12, "name": "Player 2" },
    { "id": 13, "name": "Player 3" },
    { "id": 14, "name": "Player 4" }
  ]
}

Step 3. Send teams.

{
  "teams": [
      [ 11, 12 ], [ 13, 14 ] 
  ]
}

So how is this usually dealt with? How do JSON API developers tackle this scenario?

  • What prevents you from merely sending the JSON body in your first example? – Robert Harvey Jun 10 '16 at 17:02
  • The first JSON is incorrect. – dabadaba Jun 10 '16 at 17:04
  • What do you mean? It's syntactically incorrect JSON. I just used that as an example to show what I wanted to have represented correctly in the body; and I am referring to the player1, player2 "references". – dabadaba Jun 10 '16 at 17:06
  • I don't really understand what the problem is. Use the format that is correct JSON, that is most convenient for your application, and that satisfies your particular requirements. – Robert Harvey Jun 10 '16 at 17:08
  • I don't think you are understanding what my post is about :/ – dabadaba Jun 10 '16 at 17:09
5

For inspiration, you may want to look into the way some of the json based api's (ex: json api, HAL) handle embedding.

One simple answer is to track your data in a key value store. For example

{ "/players/0" : {...}
, "/players/1" : {...}
, "/players/2" : {...}
, "/players/3" : {...}
, "/teams/0" : {...}
, "/teams/1" : {...}
}

And then you describe the players assigned to your team using local references

, "/teams/0" :
    { refs : 
        [ "/players/0"
        , "/players/1"
        ]
    }

As it happens, this scheme covers the case where you have identifiers too. Or where you only have some identifiers

, "/teams/0" :
    { refs : 
        [ "/players/0"
        , "/players/2ad8cabe-2f93-11e6-ac61-9e71128cae77"
        ]
    }

There are fancier versions of this idea (see the links).

That said, I've been down this road myself, and I really tied myself in knots until I concluded: if what you really have is a list of names, rather than a list of players, admit that to yourself, code it that way, and deal with it. It's the more honest way of representing what's going on in the domain at that point in time.

In which case, the payload of your message should look very close to:

{ "Team 1" : 
  [ "Player 1"
  , "Player 2"
  ]
, "Team 2" :
  [ "Player 3"
  , "Player 4"
  ]
}

If that makes you twitchy, remember: this isn't a description of your domain objects; it's a message. The changes it makes to your domain are a side effect. Jim Webber covers this in the introduction to this talk.

4

This is a really nice question.

The problem arises because you are modeling redundant information and try to avoid redundancy at the same time.

On the one hand, you have a collection of players

players = [{"id":"1"},{"id":"2"},{"id":"3"}]

On the other hand, you have a colletion of teams, which itself consist of subsets from players.

teams = [ {"id":"1", "players": [ players[0], players[1] ]} ]

This gives a composition:

players = [{id:1},{id:2},{id:3},{id:4}]

teams =[ {id:1, players:[players[0], players[1]]} ]

data = {players:players, teams:teams}

Look here for the Fiddle and watch the result.

As you see, the references cause redundant information in JSON.stringify, because you have redundant information in your data object.


The problem of avoiding redundancy arises when sending data to the server.

Take a step back.

What do you want to tell the server?

a) Here you have a list of teams, please persist it for me. I come back to you later. Oh, by the way, the teams contain the following players blablabla

b) Here you have a list of players. Keep 'em safe for me. I need them later to build `teams.

Your model shows, that you are not clear.

There are several usecases:

I) I want to create new players

IIa) I want to create new teams

IIb) I want to put players in teams

I) In a REST-context, you could issue a POST to /players.

IIa,b) You POST to /teams your collection of teams.

How to deal with the situation, that you want to save requests and do not issue a single POST for each creation of a new player (and an additional one for submitting the team)?

I would go for the following:

You have a collection of players: some of them have an id, indicating, that they were already persisted; some of them don't.

If you create teams, you issue only one POST request with the teams, containing the full player objects.

[{"name":"team1", "players":[{"id":"1", "name":"player1"}, "name":"player2"}]}, ... ] // you get the idea 

The server isn't interested in knowing explicitely how many players there are: it is implicitely clear: it's the sum of all players (which might be the sum of all players in all teams).

The server has to figure out, how to persist the players and how to set foreign keys (in case of relational DBs).

0

In your example, you could also wrap the players.

{
  "teams": [
    {
      "name": "Team 1",
      "players": [ 
        { "name": "Player 1" },
        { "name": "Player 2" }
      ]
    },
    {
      "name": "Team 2",
      "players": [
        { "name": "Player 3" },
        { "name": "Player 4" }
      ]
    },
  ]
}

And if you say, players and teams should be seperated because they represent independent entities and players are more than just an inherent property of a team, then you have to seperate the player post.

You tagged rest. If you consider a player to be a unique resource, it actually requires its own URI.

A resource is anything that’s important enough to be referenced as a thing in itself (s)

The user of your software will probably want to execute CRUD-operations on one or multiple players. I'd say it is pretty clear you need to make two seperate http-calls as you described in step 3.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.