3

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

2 Answers 2

2

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);

Another idea:

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...

Sign up to request clarification or add additional context in comments.

4 Comments

Although, if you want a more generic solution for all the entities, this won't work as you need to repeat the logic...
I've been thinking of something similar but it just seems nasty to load the fully serialized data into memory and then reduce it instead of just getting the reduced data
Aha, so here is an idea: JSMSerializer, by default, skips NULL values. If you could load your entity as partial object you could achieve just that.
This approach seems kind of hacky BUT.... it has this additional benefit that it will select less columns from the database and save some memory / speed up queries :-) . I'll think about this solution for a minute
1

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);
}
}

10 Comments

Nice "prototype" but it looks like it won't work with embedded collections
it's a "prototype" we're successfully using since over two years now. sorry, but you're question doesn't ask for collections - this is indeed missing in our solution currently (we handle that differently), but it should be feasible with this approach as well.
given your question this answer is perfectly fine. how would you ask for collections in your request? something like /users?fields=username,email,adress.city or what do you mean?
that's making a hure difference here - but can you tell me why my approach shouldn't be feasible here? and where do you stop? what about: /users?fields=username,email,adress.region.country.countryISO?
That's also a problem I've been thinking of. I think there should be some kind of map that you pass when serializing data (or just a simple array) so the serializer will ignore the fields that are not specified in the map. I don't know how to implement it with JMSSerializerBundle though as I'm kind of new to it. You probably need to add an exclusion strategy but I don't know how to make it work with the map
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.