Serialization of ArrayPaginator includes unwanted keys from \LimitIterator
API Platform version(s) affected: 2.6.8
Description
Getting unexpected serialized JSON output when using a custom data provider that returns an ArrayPaginator. I'm not sure if I'm missing something but after some digging here's what I found.
First, Symfony's normalizer creates a normalized array based off of the keys and values of the data being normalized. If the data being normalized is an associative array or has keys that don't start from 0 then the resulting normalized data will include those keys, otherwise it will be a standard normalized array. This is expected behavior, however...
... Since the ArrayPaginator uses a \LimitIterator, when Symfony's normalizer iterates the keys and values from the ArrayPaginator, the keys of the items being returned from the \LimitIterator are what get output. This causes the normalized/serialized data of an ArrayPaginator to include unwanted keys in collection output.
How to reproduce
- Create an API resource class with some pagination settings for testing:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
#[ApiResource(
attributes: [
"pagination_enabled" => true,
"pagination_items_per_page" => 2,
]
)]
class Foo
{
#[ApiProperty(identifier: true)]
private int|float $id;
private string $name;
/**
* @param float|int $id
* @param string $name
*/
public function __construct(float|int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
/**
* @return float|int
*/
public function getId(): float|int
{
return $this->id;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
}
- Create an
ArrayPaginatorobject with a collection of the above objects and limit the results then serialize it.
// ...
use ApiPlatform\Core\DataProvider\ArrayPaginator;
$firstResult = 2;
$maxResults = 2;
$data = new ArrayPaginator([
new Foo(1, 'foo-1'),
new Foo(2, 'foo-2'),
new Foo(3, 'foo-3'),
new Foo(4, 'foo-4'),
new Foo(5, 'foo-5'),
], $firstResult, $maxResults);
echo $serializer->serialize($data, 'json');
The above will output the following JSON (note the unwanted keys which came from the \LimitIterator):
{
"2":{
"id":3,
"name":"foo-3"
},
"3":{
"id":4,
"name":"foo-4"
}
}
Possible Solution
End-users can possibly use a custom serializer or normalizer. Another option is for the lib's ArrayPaginator to instead use an extension of \LimitIterator similar to the following:
<?php
declare(strict_types=1);
namespace App\Foo;
/**
* Custom limit iterator that outputs 0'th based keys
*/
class LimitIterator extends \LimitIterator
{
private int $keyPosition = 0;
/**
* @inheritDoc
*/
public function key()
{
return $this->keyPosition;
}
/**
* @inheritDoc
*/
public function next()
{
$this->keyPosition ++;
parent::next();
}
/**
* @inheritDoc
*/
public function rewind()
{
$this->keyPosition = 0;
parent::rewind();
}
}
If I copy API Platform's ArrayPaginator and have it use the above LimitIterator, then the same test as above returns data as expected:
// A copy of ArrayPaginator that uses the above LimitIterator
use App\Api\Data\ArrayPaginator;
$firstResult = 2;
$maxResults = 2;
$data = new ArrayPaginator([
new Foo(1, 'foo-1'),
new Foo(2, 'foo-2'),
new Foo(3, 'foo-3'),
new Foo(4, 'foo-4'),
new Foo(5, 'foo-5'),
], $firstResult, $maxResults);
echo $serializer->serialize($data, 'json');
The above will output the data as expected without the keys:
[
{
"id":3,
"name":"foo-3"
},
{
"id":4,
"name":"foo-4"
}
]