8

As the title says, I am trying to make a run-time decision on whether or not to include fields in the serialization. In my case, this decision will be based on permissions.

I am using Symfony 2, so what I'm looking to do is add an additional annotation called @ExcludeIf which accepts a security expression.

I can handle the annotation parsing and storing of the meta data, but I am not able to see how to integrate a custom exclusion strategy with the library.

Any suggestions?

Note: exclusion strategies are an actual construct in the JMS codebase, I just haven't been able to figure out the best way to integrate an extra on top of the others

PS: I had asked about this before and was pointed to using groups. For various reasons this is a very poor solution for my needs.

Jason McClellan
  • 2,931
  • 3
  • 23
  • 32
  • You could use a custom subscribing handler for your specific object that allows or removes specified items. See http://jmsyst.com/libs/serializer/master/handlers – qooplmao Feb 21 '14 at 01:57
  • From what I can tell, this won't allow me to skip a field. I could set it to null at best, but that isn't semantically the same and not what I want. – Jason McClellan Feb 21 '14 at 17:33

2 Answers2

10

You just have to create a class that implements JMS\Serializer\Exclusion\ExclusionStrategyInterface

<?php

namespace JMS\Serializer\Exclusion;

use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Context;

interface ExclusionStrategyInterface
{
    /**
     * Whether the class should be skipped.
     *
     * @param ClassMetadata $metadata
     *
     * @return boolean
     */
    public function shouldSkipClass(ClassMetadata $metadata, Context $context);

    /**
     * Whether the property should be skipped.
     *
     * @param PropertyMetadata $property
     *
     * @return boolean
     */
    public function shouldSkipProperty(PropertyMetadata $property, Context $context);
}

In your case, you can implement your own custom logic in the shouldSkipProperty method and always return false for shouldSkipClass.

Example of implementation can be found in the JMS/Serializer repository

We will reference the created service as acme.my_exclusion_strategy_service below.


In your controller action:

<?php

use Symfony\Component\HttpFoundation\Response;
use JMS\Serializer\SerializationContext;

// ....

$context = SerializationContext::create()
    ->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'));

$serial = $this->get('jms_serializer')->serialize($object, 'json', $context);

return new Response($serial, Response::HTTP_OK, array('Content-Type' => 'application/json'));

Or if you are using FOSRestBundle

<?php

use FOS\RestBundle\View;
use JMS\Serializer\SerializationContext;

// ....

$context = SerializationContext::create()
    ->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'))

$view = new View($object);
$view->setSerializationContext($context);

// or you can create your own view factory that handles the creation
// of the context for you

return $this->get('fos_rest.view_handler')->handle($view);
rolebi
  • 1,181
  • 9
  • 13
  • Thanks! I had figured it out, thankfully, but I should have came back to update the question. – Jason McClellan May 09 '14 at 17:29
  • JMS\Serializer\Context is an abstract class so it cannot be instantiated as shown in your example. There's also no example whatsoever of the "acme.my_exclusion_strategy_service" – Nicolas Jul 18 '16 at 11:26
  • @Nicolas Thank you for pointing out the error about SerializationContext ! I've fixed it and add some clarifications. I won't provide an example of implementation of the interface since this is pretty straight forward (what is not trivial is the associated custom logic). Nevertheless you can look at [GroupsExclusionStrategy](https://github.com/schmittjoh/serializer/blob/master/src/JMS/Serializer/Exclusion/GroupsExclusionStrategy.php) that is provided by JMS for example – rolebi Jul 19 '16 at 14:25
  • The logic I referred to in this question originally was implemented in the following: https://github.com/jmcclell/JLMSerializerExpression Its usage is made more clear in the associated Symfony Bundle: https://github.com/jmcclell/JLMSerializerExpressionBundle – Jason McClellan Jul 19 '16 at 21:43
4

As of jms/serializer 1.4.0, the symfony expression language is integrated in its core.

The feature is documented at http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#dynamic-exclusion-strategy and this allows to use runtime exclusion strategies.

An example taken from the documentation is:

class MyObject
{
    /**
     * @Exclude(if="service('user_manager_service').getSomeRuntimeData(object)")
     */
    private $name;

   /**
     * @Expose(if="service('request_stack').getCurrent().has('foo')")
     */
    private $name2;
}

I this example, the services user_manager_service and request_stack are invoked at runtime, and depending on the return (true or false), the property will be exposed or not.

With the same expression language, as of 1.6.0 is possible also to use virtual properties via expression language. Documented at http://jmsyst.com/libs/serializer/master/reference/annotations#virtualproperty allows to add on the fly data coming from external services

Asmir Mustafic
  • 533
  • 4
  • 14