Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expressing empty relationships in the response #20

Closed
emil-nasso opened this issue Apr 28, 2016 · 5 comments
Closed

Expressing empty relationships in the response #20

emil-nasso opened this issue Apr 28, 2016 · 5 comments

Comments

@emil-nasso
Copy link
Contributor

In the json response format used by the current version of yin, i am running into problems that i think is caused by not following the json api specification.

The problem is in the relationships data. Lets take a look at this example:

"data": {
    "type": "stuffs",
    "id": "st1234",
    "links": {
      "self": "/stuffs/st1234"
    },
    "attributes": {
      "name": "some stuff",
    },
    "relationships": {
      "account": [],
    }
  }

In this case, the account-relationsship is not included, and is empty, as can be seen here (or above):

"account": [],

When including the data by adding a include query parameter with the value account, you get this:

"account": {
  "data": {
    "type": "accounts",
    "id": "lb1959846"
  }
},

The behaviour that is causing the issues is when the relationship is empty, when there is no data for the relationship. I that case, this will be returned by yin:

"account": [],

This looks identical to what you get when you don't include the relationship at all. Therefor, it is impossible for a client to judge if the relationship has been empty, or if it has simply not been included.

In my case, my client warns the user if they are trying to use data from a relationship that has not been loaded. This make it impossible to know if the object has a relationship with an account, or if it's empty simply because the user choose not to include it.

This is in the specification here:
http://jsonapi.org/format/#fetching-relationships-responses-200

There are examples for both a to-one relationship and a to-many relationship.

If the relationship is included but empty, the data of the response should be like this for a to-one:

"account": {
  "data": null
}

and like this for a to-many:

"account": {
  "data": []
}

With this, a client would see that the data field is in the response and know that the relationship has been included. If the data is null or an empty array, it just means that the relationship is empty.

I have a working patch for this. I will try to create a pull-request out of it. It needs more sets of eyes.

emil-nasso pushed a commit to emil-nasso/yin that referenced this issue Apr 28, 2016
* When `transformData` returns an empty data-set (null or and empty array) include this in the response.
* The empty data for a ToManyRelationship is [] not null.
@kocsismate kocsismate added bug and removed bug labels Apr 29, 2016
@kocsismate
Copy link
Member

kocsismate commented Apr 29, 2016

Sorry for the delay, I only had time to play with your patch now!

Thank your really much for your efforts, but before accepting your PR, let's clarify what the actual problem is, because I have some difficulties to understand it properly. :(

I tried the current implementation and yours with the book example when an empty to-one and and an empty to-many relationship were included and when they weren't; and found the following in both cases:

The current implementation omits the data part when it is empty because of this discussion: #5, while your version properly returns the data key independently if the relationship was included or not. Did I get it right? But wasn't your aim to differentiate a relationship if it was included or not?

There might be a chance that you (or me :) ) are confusing something about inclusion of resources. The include query parameter doesn't have any effect on the relationship itself, it only affects the included member. You can only omit a relationship if you use sparse fieldsets with the fields query param.

Another source of confusion for me is that you linked the specification about fetching a resource's relationships (when you visit the {{ RESOURCE }}/relationships/{{ RELATIONSHIP NAME }} link), but the original problem was about a relationship when fetching a resource. Although these are interconnected things, I wasn't sure when the problem really happens.

Could you please answer my questions? I like your PR very much, because you solved a problem what I couldn't, but I would need more details to learn about your intentions.

@emil-nasso
Copy link
Contributor Author

Sorry about the confusion. The documentation is not 100% clear on this. The thing i was trying to point at was that the data-attribute should be included in the output, even if it is empty.

I created an example using the user/book code in the repo.

As there doesn't seem to be any empty relationships in the example i had to make a small modification.

In the UserRepository i changed the second user to this:

[
    "id" => "2",
    "firstname" => "Jane",
    "lastname" => "Doe",
    "contacts" => []
]

I changed the contacts to an empty array, so that the user simply has no contact information.

When just requesting this url to get the users via the url http://127.0.0.1:38080?path=/users i get this output:

{
  "links": {
    "self": "/?path=/users&page[number]=1&page[size]=10",
    "first": "/?path=/users&page[number]=1&page[size]=10",
    "last": "/?path=/users&page[number]=1&page[size]=10",
    "prev": null,
    "next": null
  },
  "data": [
    {
      "type": "user",
      "id": "1",
      "attributes": {
        "firstname": "John",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/1/contacts",
            "self": "/?path=/users/1/relationships/contacts"
          }
        }
      }
    },
    {
      "type": "user",
      "id": "2",
      "attributes": {
        "firstname": "Jane",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/2/contacts",
            "self": "/?path=/users/2/relationships/contacts"
          }
        }
      }
    }
  ]
}

This is all good. The users are included but no data is present for the contacts, as they have not been included.

If i however include the data, the problem shows itself (via the url 127.0.0.1:38080?path=/users&include=contacts):

{
  "links": {
    "self": "/?path=/users&page[number]=1&page[size]=10",
    "first": "/?path=/users&page[number]=1&page[size]=10",
    "last": "/?path=/users&page[number]=1&page[size]=10",
    "prev": null,
    "next": null
  },
  "data": [
    {
      "type": "user",
      "id": "1",
      "attributes": {
        "firstname": "John",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/1/contacts",
            "self": "/?path=/users/1/relationships/contacts"
          },
          "data": [
            {
              "type": "contact",
              "id": "100"
            },
            {
              "type": "contact",
              "id": "101"
            },
            {
              "type": "contact",
              "id": "102"
            }
          ]
        }
      }
    },
    {
      "type": "user",
      "id": "2",
      "attributes": {
        "firstname": "Jane",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/2/contacts",
            "self": "/?path=/users/2/relationships/contacts"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "contact",
      "id": "100",
      "links": {
        "self": "/?path=/contacts/100"
      },
      "attributes": {
        "phone": "+123456789"
      }
    },
    {
      "type": "contact",
      "id": "101",
      "links": {
        "self": "/?path=/contacts/101"
      },
      "attributes": {
        "email": "john.doe@example.com"
      }
    },
    {
      "type": "contact",
      "id": "102",
      "links": {
        "self": "/?path=/contacts/102"
      },
      "attributes": {
        "email": "secret.doe@example.com"
      }
    }
  ]
}

The first user has a data-attribute in its contacts relationship, but the second one doesn't. It looks exactly like it did when the contacts were not included at all. Because of this, a client doesn't know if the data was simply not requested, or if it was requested but is empty.

I how switched to the branch for my PR and did the same request, again with the included contacts and get this output:

{
  "links": {
    "self": "/?path=/users&page[number]=1&page[size]=10",
    "first": "/?path=/users&page[number]=1&page[size]=10",
    "last": "/?path=/users&page[number]=1&page[size]=10",
    "prev": null,
    "next": null
  },
  "data": [
    {
      "type": "user",
      "id": "1",
      "attributes": {
        "firstname": "John",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/1/contacts",
            "self": "/?path=/users/1/relationships/contacts"
          },
          "data": [
            {
              "type": "contact",
              "id": "100"
            },
            {
              "type": "contact",
              "id": "101"
            },
            {
              "type": "contact",
              "id": "102"
            }
          ]
        }
      }
    },
    {
      "type": "user",
      "id": "2",
      "attributes": {
        "firstname": "Jane",
        "surname": "Doe"
      },
      "relationships": {
        "contacts": {
          "links": {
            "related": "/?path=/users/2/contacts",
            "self": "/?path=/users/2/relationships/contacts"
          },
          "data": []
        }
      }
    }
  ],
  "included": [
    {
      "type": "contact",
      "id": "100",
      "links": {
        "self": "/?path=/contacts/100"
      },
      "attributes": {
        "phone": "+123456789"
      }
    },
    {
      "type": "contact",
      "id": "101",
      "links": {
        "self": "/?path=/contacts/101"
      },
      "attributes": {
        "email": "john.doe@example.com"
      }
    },
    {
      "type": "contact",
      "id": "102",
      "links": {
        "self": "/?path=/contacts/102"
      },
      "attributes": {
        "email": "secret.doe@example.com"
      }
    }
  ]
}

The difference here is that both users has their contacts relationship included and the same json structure. You can see the data-attribute in the relationship for both users. As the second user doesn't have any contact information, the relationship is empty ( "data": []). If this were a to-one-relationship, the result would be "data": null.

While looking at this, it seems like the problem is there too when directly fetching the relationships for the second user, via this url 127.0.0.1:38080?path=/users/2/relationships/contacts.

In that case the output looks like this:

{
  "links": {
    "related": "/?path=/users/2/contacts",
    "self": "/?path=/users/2/relationships/contacts"
  }
}

The example i linked in my previous post looks like this:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "links": {
    "self": "/articles/1/relationships/tags",
    "related": "/articles/1/tags"
  },
  "data": []
}

The data-attribute is missing here. My PR doesn't affect this. It could prehaps be looked at later, separate from this PR?

@kocsismate
Copy link
Member

OK, thank you for this detailed comparison! Now, I understood what you exactly meant by 'a client doesn't know if the data was simply not requested, or if it was requested but is empty' and this will be a really nice addition to v0.10.5!

kocsismate added a commit that referenced this issue May 3, 2016
@kocsismate
Copy link
Member

And I'll have a look at the data member when fetching relationships in a separate issue!

@emil-nasso
Copy link
Contributor Author

Great. Many thanks! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants