Reputation: 1840
I'm building a REST API and I would like to give my users a possibility to choose which fields to return via a URL parameter, e.g
/users?fields=username,email,address.city,address.country
Is there any way to accomplish such functionality with JMSSerializerBundle?
// EDIT
Please note the embedded collections
Upvotes: 3
Views: 1713
Reputation: 20191
I don't think this the job for JMSSerializer
, at least no entirely. Instead, what I would do is:
// Do not serialize into JSON or XML, but to PHP array
$userSerialized = $jmsSerializer->toArray($user);
// Turn "username,email" into ['username' => 0, 'email' => 1, ... ]
$fields = array_flip(explode($request->query->get('fields')));
$userSerializedFiltered = array_intersect_key($userSerialized, $fields);
// Finally, put it into desired format, JSON for example:
$json = json_encode($userSerializedFiltered);
You could utilize Doctrine Partial objects:
$user = $em->createQuery("select partial u.{" . $fields . "} from MyApp\Domain\User u")->getResult();
$serialized = $jmsSerializer->serialize($user, 'json');
Hope this helps...
Upvotes: 2
Reputation: 4089
EDIT: This answer only covers the initial question which didn't ask for deeper levels of serialization. I'll keep it anyway as it might help others struggling with that initial question as well.
We did exactly the same in a pretty generic way.
We extended the ViewHandler to read from the current Request if 'fields' has been attached as a parameter and added a ExclusionStrategy to the SerializationContext.
It's worth to notice that this approach is working with FOS Rest Bundle 1.7.7 (we didn't migrate to the newest JMS version so far), Symfony > 2.8 and JMSSerializerBundle 1.1.0 - but it shouldn't be too hard to migrate this approach to any other combination as well.
class ViewHandler extends \FOS\RestBundle\View\ViewHandler
{
/**
* Extends ViewHandler, adds the exclusion strategies FieldListExclusionStrategy to the SerializationContext.
*
* Reads Request Parameter "fields" (comma separated list) and parses it into an array. Does some clean-up on parameter
*/
protected function getSerializationContext(View $view)
{
$context = $view->getSerializationContext();
$request = $this->container->get('request_stack')->getCurrentRequest();
if ($request->isMethod('GET') && $request->query->has('fields')) {
$fieldList = explode(',', $request->query->get('fields'));
array_walk($fieldList, array(&$this, 'cleanString')); //clean special characters except - and _
$fieldList = array_filter($fieldList); // remove empty elements
$context->addExclusionStrategy(new FieldListExclusionStrategy($fieldList));
}
$view->setSerializationContext($context);
return parent::getSerializationContext($view);
}
/**
* Helper to remove special characters from String, Compatible with array_walk
*
* @param string $string -
* @param mixed $key -needed to be compatible with array_walk without raising a notice. (hands in 2 parameters)
*
* @return mixed
*/
private function cleanString(&$string, $key)
{
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
$string = preg_replace('/[^A-Za-z0-9\-\_]/', '', $string); // Removes special chars.
return preg_replace('/-+/', '-', $string); // Replaces multiple hyphens with single one.
}
}
And this is the FieldListExclusionStrategy Class:
class FieldListExclusionStrategy implements ExclusionStrategyInterface
{
/**
* @var array
*/
private $fields = array();
/**
* @var int
*/
private $maxDepth;
/**
* FieldListExclusionStrategy constructor.
*
* @param array $fields
*/
public function __construct(array $fields)
{
$this->maxDepth = 1;
$this->fields = $fields;
}
/**
* Whether the class should be skipped.
*
* @param ClassMetadata $metadata
* @param Context $context
* @return boolean
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
return false;
}
/**
* Whether the property should be skipped.
*
* @param PropertyMetadata $property
* @param Context $context
*
* @return boolean
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
if (0 === count($this->fields)) {
return false;
}
if ($context->getDepth() > $this->maxDepth) {
return false;
}
$name = $property->serializedName ?: $property->name;
return !in_array($name, $this->fields, true);
}
}
Upvotes: 1