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

[WIP] Add JsonApiSerializer #108

Closed
wants to merge 1 commit into from
Closed

[WIP] Add JsonApiSerializer #108

wants to merge 1 commit into from

Conversation

willdurand
Copy link
Owner

URL based, see: http://jsonapi.org/format/#url-based-json-api

Limitations

This serializer works but it requires a few conditions:

  1. The serializer MUST take an array to serialize, not just a resource, because of the JSON API specification:
$user = new User(123);

// we can't just pass `$user` here
$hateoas->serialize(array('users' => $user), 'json');
  1. href values for link relations MUST be identifiers (still because of the JSON API spec):
/**
 * @Hateoas\Relation("posts", href = "expr(object.getPostIds())")
 */
class User
{
    public function getPostIds()
    {
        return [ 4, 5, 10 ];
    }
}

The output would be:

{
    "users": [{
        "id": 123,
        "links": {
            "posts": [ 4, 5, 10 ]
        }
    }]
}
  1. In order to define top level links, you MUST set a topLevel attribute:
/**
 * @Hateoas\Relation(
 *     "posts",
 *     href = "http://example.com/{id}/posts",
 *     attributes = { "topLevel" = true, "type" = "posts" }
 * )
 *
 * ...
 */
class User
{
    // ...
}

The output would be:

{
    "links": {
        "posts": {
            "href": "http://example.com/{id}/posts",
            "type": "posts"
        }
    },
    "users": {
        "id": 123,
        "links": {
            "posts": [
                4,
                5,
                10
            ]
        }
    }
}

I guess these requirements are acceptable.

WDYT?

@willdurand
Copy link
Owner Author

ping @adrienbrault @lsmith77

}

$visitor->addData('links', $serializedLinks);
$visitor->setRoot(array('links' => $topLevelSerializedLinks));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to check if there is already a root, otherwise you would override existing roots, cf: https://github.com/schmittjoh/serializer/blob/master/src/JMS/Serializer/Handler/FormErrorHandler.php#L128

@adrienbrault
Copy link
Contributor

When returning a list of documents from a response, a top-level "links" object can specify a URL template that should be used for all documents.

I don't see where it is written that there has to be top level links.

About your limitation 2: the issue is that someone that build an api for this spec, won't be able to switch to hal or something else because everything will be broken. (ids in href etc)

Using topLevel in the link attribute is a hack since the attributes are meant to be serialized with the link. (When using another serializer, we would get topLevel in links.)

About your limitation 1, why not use the Representation\SimpleCollection for this ?

What about serializing the self link in both the href and id property ?

@lsmith77
Copy link

yeah, i think it would be awesome if eventually we could get rid of these 2 limitations. in the serializer there are for example some xml specific mapping rules. this could be used to for example allow mapping the json api key so that some handler then wraps the value into an array with the key solving limitation 1)

solving 2) seems to be a bit trickier, but here maybe here we can again figure out some output format specific mapping?

@willdurand
Copy link
Owner Author

For 2), we might be able to use exclusion strategies...

@koemeet
Copy link

koemeet commented Mar 24, 2014

I've tried something so that the id's for embedded resources are populated automatically, the only problem still, it's still depended on the method getId, I don't know if that is bad. I thought maybe I could share this. I'm currently using this in one of my own project, trying to create a better solution when I have more time (maybe use strategies for populating the ids?).

Also a problem is that the properties for the relations needs to be excluded, it would be nice if it could override them. If you do not exclude them, you will get an error saying that the property already contains data.

  1. What about the include query parameter, it would be nice to have some integrated solutions for popular ORMs. Some sort of automatic embedds, if that makes sense.
public function serializeEmbeddeds(array $embeddeds, JsonSerializationVisitor $visitor, SerializationContext $context)
{
    $serializedEmbeds = array();
    foreach ($embeddeds as $embed) {
        $data = $embed->getData();
        $ids = array();

        if ($data instanceof \Traversable) {
            foreach ($data as $object) {
                if (is_callable(array($object, 'getId'))) {
                    $ids[] = $object->getId();
                }
            }
        } else {
            if (is_callable(array($data, 'getId'))) {
                $ids[] = $data->getId();
            }
        }

        if (!empty($ids)) {
            $visitor->addData($embed->getRel(), $ids);
        }
    }

    $serializedEmbeds[$embed->getRel()] = $context->accept($embed->getData());
    $visitor->setRoot(array('linked' => $serializedEmbeds));
}

Of course, we need to replace existing models inside the root, maybe just a simple array_replace_recursive?

$serializedEmbeds[$embed->getRel()] = $context->accept($embed->getData());
}

$visitor->setRoot(array('linked' => $serializedEmbeds));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs an array_replace_recursive, otherwise linked is overwritten every time.

@Alabme
Copy link

Alabme commented May 6, 2014

Hi, I've spent a while to take a look at Hateoas library and at issues mentioned above, and this is what I think:

  1. If You want to support more standards You have to stop thinking in "links to resources" terms, and start thinking in "relations with resources" . Great example here is json-hal and json-api. These two formats are so much different. In json-api there are a lot of different ways to represent relations to another resources: as id, as array of ids, as href link, as object with all together mentioned before, so "links to resources" way becoming a little messy with json-api...

To achieve that it need to be defined some common attributes for resource in every single format. I suppose such a list may look like this:

  • id,
  • name,
  • collectionName,
  • href,
  • collectionHref.

A "name" and "collectionName" is for naming conventions purposes (and some other stuff) in various formats.
Now, using annotations (e.g. of class User):

@Annotation\Resource(
 *      id = "expr(object.getId())",
 *      name = "user",
 *      collectionName = "users",
 *      href = "/users/{id}",
 *      collectionHref = "/users",
 *      binds = {
 *          "{id}" = "expr(object.getId())"
 *      }

This is about "resource", while "relation" could look like this:

@Annotation\Relation(
 *      id = "expr(object.getProjectId())",
 *      class = "Model\Project",
 *      type = "toOne",
 *      binds = {
 *          "{id}" = "expr(object.getProjectId())"
 *      }
 * )

A "class" attribute stands for a class where we can find some "@annotation\Resource", with other, earlier mentioned attributes as "name", "collectionName", "href", etc. A "type" attribute stands for type of relation (toOne, toMany). In this approach we have almost all needed information about resource and its relations. Of course "Annotation\Relation" could extends "Annotation\Resource", so attributes given here will override those ones defined in "class" class.

  1. I think You should separate serialization process from data formatting process. JMS Serializer is a great tool, but I would use it to serialize an given object to just an array with all resource and relations data and pass this array to SomeDataFormater, which will transform it to desired format (json-hal, json-api, etc) and return as json or xml.

Maybe there is something that I've missed, but I'm pretty sure it approach may help to resolve almost all issues mentioned above.

I would be grateful for Your opinion about that.

Best regards,
Tomek Bedkowski

@koemeet
Copy link

koemeet commented May 12, 2014

@Alabme I like the idea that you minimize that differences in configuration between hal and json-api. When I created my serializer for this bundle to support json-api I also had to override the LinkFactory for the same reason you mentioned. The current Link object does not contain very much information.

Also, the "relation" idea has a better naming than "links". In HAL the name links would be fine, but as you said, json-api does also other things with links.

This library already has a good way of seperating data formatting from serializing, but the naming of things and the intent of it should change.

Also support for things like include should be there too. I need all those things, so when I have something that I think works good I will post it here.

Currently I am at work, but I will think things through too. Like your ideas!

@willdurand
Copy link
Owner Author

I like the ideas too!

@rufinus
Copy link

rufinus commented Jun 22, 2014

(+1)

@ulilicht
Copy link

Hi,

I'd like to ask about the current state of this - I am planning a REST backend for a bigger project at the moment and i've to decide with which standard we want to go.

  • What are the open todos of this branch?
  • Is someone working on it or is it even possible to support json-api with HATEOAS? (also what is the current state of Fundamental issues? #126 ?
  • How could we contribute?

Best Regards, Uli

@pmcjury
Copy link

pmcjury commented Jul 24, 2014

+1

@skqr
Copy link

skqr commented Sep 15, 2014

+1 - we're hoping to use this at http://www.gointegro.com. I was about to start work on the exact same thing. You rock, @willdurand.

@skqr
Copy link

skqr commented Nov 20, 2014

Hey, guys. I made this HATEOAS lib / bundle inspired by the effort on this PR. It's a bit different and more specific, but I thought you might want to check it out.

@koemeet
Copy link

koemeet commented Jun 1, 2015

Json api is stable now (1.0)! I might be able to work on some of this in about two or three months.

@willdurand willdurand closed this Dec 16, 2015
@goetas goetas deleted the json-api branch December 25, 2018 10:28
@goetas goetas restored the json-api branch December 25, 2018 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants