使用JMSSerializerBundle对指定字段进行序列化

3

我正在构建一个REST API,并希望为用户提供通过URL参数选择要返回的字段的可能性,例如:

/users?fields=username,email,address.city,address.country

有没有办法使用JMSSerializerBundle实现这样的功能?
// 编辑
请注意嵌入式集合
2个回答

2

我认为这不完全是JMSSerializer的工作。相反,我会这样做:

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

另一个想法:

您可以利用Doctrine部分对象

$user = $em->createQuery("select partial u.{" . $fields . "} from MyApp\Domain\User u")->getResult();
$serialized = $jmsSerializer->serialize($user, 'json');

希望这有所帮助...

虽然,如果你想要一个适用于所有实体的更通用的解决方案,这个方法就不行了,因为你需要重复逻辑... - Jovan Perovic
我一直在考虑类似的事情,但是将完全序列化的数据加载到内存中然后进行缩减,而不是仅获取缩减后的数据,似乎很麻烦。 - user2394156
哦,这里有一个想法:JSMSerializer默认跳过NULL值。如果您可以将实体加载为部分对象,则可以实现此目的。 - Jovan Perovic
这种方法似乎有点hacky,但是......它还有一个额外的好处,它将从数据库中选择更少的列并节省一些内存/加快查询速度:-)。我会考虑这个解决方案一分钟。 - user2394156

1

编辑:此答案仅涵盖最初的问题,该问题没有要求更深层次的序列化。我仍然会保留它,因为它可能也有助于其他人解决这个最初的问题。

我们以一种相当通用的方式进行了完全相同的操作。

我们扩展了ViewHandler,从当前请求中读取是否已将“fields”附加为参数,并向SerializationContext添加了一个ExclusionStrategy

值得注意的是,这种方法适用于FOS Rest Bundle 1.7.7(我们迄今没有迁移到最新的JMS版本),Symfony> 2.8和JMSSerializerBundle 1.1.0-但是将这种方法迁移到任何其他组合也不应该太难。

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

这是FieldListExclusionStrategy类:

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

这是我们已经成功使用超过两年的“原型”。抱歉,但您的问题并未要求收集 - 这确实在我们的解决方案中目前缺失(我们以不同的方式处理它),但是使用这种方法也应该是可行的。 - LBA
根据你的问题,这个答案非常好。你想在请求中如何请求集合?像这样 /users?fields=username,email,address.city 还是你指的是什么? - LBA
这里有一个很大的区别 - 但你能告诉我为什么我的方法在这里不可行吗?你会在哪里停止呢?比如:/users?fields=username,email,adress.region.country.countryISO? - LBA
这也是我一直在思考的问题。我认为应该有一种地图,在序列化数据时(或只是一个简单的数组),你可以传递这个地图,使得序列化程序将忽略地图中未指定的字段。然而,我不知道如何使用 JMSSerializerBundle 实现它,因为我对它还比较陌生。可能需要添加一个排除策略,但我不知道如何与地图配合使用使其起作用。 - user2394156
所以,请仔细阅读我的代码 - 你可以按照那种方式进行扩展。但我不建议这样做,因为你将不得不在结果中提及所有字段(这可能会是一个相当长的列表)。 - LBA
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接