Buy

Taking Control of the Serializer

The serializer is... mostly working. But we have some test failures:

Error reading property "tagLine" from available keys (id, nickname
avatarNumber, powerLevel)

Huh. Yea, if you look at the response, tagLine is mysteriously absent! Where did you go dear tagLine???

So the serializer works like this: you give it an object, and it serializes every property on it. Yep, you can control that - just hang on a few minutes. But, if any of these properties is null, instead of returning that key with null, it omits it entirely.

Fortunately, that's easy to change. Go into BaseController. In serialize() create a new variable called $context and set that to a new SerializationContext(). Call setSerializeNull() on this and pass it true. To finish this off, pass that $context as the third argument to serialize():

133 lines src/AppBundle/Controller/BaseController.php
... lines 1 - 123
protected function serialize($data, $format = 'json')
{
$context = new SerializationContext();
$context->setSerializeNull(true);
return $this->container->get('jms_serializer')
->serialize($data, $format, $context);
}
... lines 132 - 133

Think of the SerializationContext as serialization configuration. It doesn't do a lot of useful stuff - but it does let us tell the serializer to actually return null fields.

So run the whole test suite again and wait impatiently:

phpunit -c app

ZOMG! They're passing!

Serialization Annotations

But something extra snuck into our Response - let me show you. In testGETProgrammer(), at the end, add $this->debugResponse(). Copy that method name and run just it:

phpunit -c app --filter testGETProgrammer

Ah, the id field snuck into the JSON. Before, we only serialized the other four fields. So what if we didn't want id or some property to be serialized?

The solution is so nice. Go back to the homepage of the bundle's docs. There's one documentation gotcha: the bundle is a small wrapper around the JMS Serializer library, and most of the documentation lives there. Click the documentation link to check it out.

This has a great page called Annotations: it's a reference of all of the ways that you can control serialization.

@VirtualProperty and @SerializedName

One useful annotation is @VirtualProperty. This lets you create a method and have its return value serialized. If you use that with @SerializedName, you can control the serialized property name for this or anything.

For controlling which fields are returned, we'll use @ExclusionPolicy. Scroll down to the @AccessType code block and copy that use statement. Open the Programmer entity, paste this on top, but remove the last part and add as Serializer:

189 lines src/AppBundle/Entity/Programmer.php
... lines 1 - 5
use JMS\Serializer\Annotation as Serializer;
... lines 7 - 189

This will let us say things like @Serializer\ExclusionPolicy. Add that on top of the class, with "all".

189 lines src/AppBundle/Entity/Programmer.php
... lines 1 - 5
use JMS\Serializer\Annotation as Serializer;
... line 7
/**
... lines 9 - 12
* @Serializer\ExclusionPolicy("all")
*/
class Programmer
... lines 16 - 189

This says: "Hey serializer, don't serialize any properties by default, just hang out in your pajamas". Now we'll use @Serializer\Expose() to whitelist the stuff we do want. We don't want id - so leave that. Above the $name property, add @Serializer\Expose(). Do this same thing above $avatarNumber, $tagLine and $powerLevel:

188 lines src/AppBundle/Entity/Programmer.php
... lines 1 - 14
class Programmer
{
... lines 17 - 25
/**
... lines 27 - 29
* @Serializer\Expose
*/
private $nickname;
... line 33
/**
... lines 35 - 37
* @Serializer\Expose
*/
private $avatarNumber;
... line 41
/**
... lines 43 - 45
* @Serializer\Expose
*/
private $tagLine;
... line 49
/**
... lines 51 - 53
* @Serializer\Expose
*/
private $powerLevel = 0;
... lines 57 - 186
}

And my good buddy PhpStorm is telling me I have a syntax error up top. Whoops, I doubled my use statements - get rid of the extra one.

With this, the id field should be gone from the response. Run the test!

phpunit -c app --filter testGETProgrammer

No more id! Take out the debugResponse(). Phew! Congrats! We only have one resource, but our API is kicking butt! We've built a system that let's us serialize things easily, create JSON responses and update data via forms.

Oh, and the serializer can also deserialize. That is, take JSON and turn it back into an object. I prefer to use forms instead of this, but it may be another option. Of course, if life gets complex, you can always just handle incoming data manually without forms or deserialization. Just keep that in mind.

We also have a killer test setup that let's us write tests first without any headache. We could just keep repeating what we have here to make a bigger API.

But, there's more to cover! In episode 2, we'll talk about errors: a fascinating topic for API's and something that can make or break how usable your API will be.

Ok, seeya next time!

Leave a comment!

  • 2016-09-30 weaverryan

    Hey Tom!

    Definitely - good question :). There are a few reasons:

    1) Data transformers - the idea that your user might send you an "id", but on your object, that property is itself an object. The EntityType is built to do this type of transformation

    2) Similar to the above, I find that your output doesn't always match your *input*. I mean, if I literally looked at the JSON for a GET /blog/{id} endpoint and compared it with the JSON that I send to *create* a blog post, they will differ more than you might think. So, at first, it seems kind of awesome to have one model class that you can serialize for output and deserialize for input. But in reality, it's not often that simple, and the form gives you that layer to hide/show fields or add other transformations.

    If I summarize these two reasons, it comes down to this: the serializer is "stupid" by design: it simply takes the JSON (when deserializing) and puts it onto an object. Unless that JSON and your object match perfectly, it gets tough. On the other hand, we going from your model to JSON is often easier: we typically design our model classes to match the JSON we want with little or no effort.

    But, not everyone agrees - that's why the deserializer exists! But I like having a model class that models my output, and a form class that models my input.

    Cheers!

  • 2016-09-28 Tom Friedhof

    You mention at the end of this tutorial that you prefer to use forms instead of the Serializer to turn the json back into an object. Can you elaborate on why you prefer to use the form method over the serializer?

  • 2016-06-20 Vlad

    Thanks, Ryan!

  • 2016-06-18 weaverryan

    Hey Vlad!

    This is something we're still working on - right now the "Updated" status (unless a course is being actively released still) usually is not significant (we probably tweaked something insignificant). We plan to publish an actual "changelog" whenever we make significant changes in the future. It's on the list!

    Cheers!

  • 2016-06-16 Vlad

    Hi Ryan,

    When you make updates to courses, how do I know what's been updated?

    I've finished some of the courses, but periodically see "Updated 3 days ago", or so, don't see any new videos, so don't know what's been updated.

    Thank you!

  • 2016-05-25 weaverryan

    Hi Roberto!

    Yes, very fair question :). First, there is no performance difference or flexibility between the formats. So, it *does* come down to developer preference. I like annotations because I like having my configuration right next to the thing it's configuring. For serializing, you can immediately see what properties are being serialized, without needing to find another configuration file. Route & Doctrine annotations also have that same advantage.

    About the coupling idea, I tend to think that idea is over-hyped. You *are* using Symfony, and it's massively unlikely that you'll need to switch suddenly to using something else. And even if you *did* need this, having your configuration in YAML instead of annotations won't make much difference - it would be pretty easy to quickly delete the annotations from your class if you decided to use a different library for serializing. Decoupling from your framework is really important if you're sharing your code (you don't want to force your serialization configuration on another use, just so they can use your class) - but purposefully *coupling* your code to your framework - along with use a nice layer of services, that's key - is a great way to be pragmatic and stay productive. That's subjective of course - but that at least shows you why I've made these choices :).

    Cheers!

  • 2016-05-25 Roberto Briones Arg├╝elles

    I notice that you always use the Annotation way, I'm trying to avoid in order to use configuration files in yml, I think that way is more decoupled from the framework or tool, but I don't really know for sure, can you tell me why are you using Annotations?

  • 2015-09-15 Christophe Lablancherie

    Hi :) Thanks for the answer. Well i've just made this after my question and that's correct, it's works :)
    Thank's you for the screencast !

  • 2015-09-15 weaverryan

    Great question actually! Try this out and let me know what you find: https://github.com/schmittjoh/...

    Cheers!

  • 2015-09-15 Christophe Lablancherie

    Hi, how could we implement the ExclusionPolicy with FOSUserBundle ? I can't "hide" in the serialization, the password, salt etc... :'(