core
core copied to clipboard
JsonLD: plain array converted to hydra:member inside Get operation in custom StateProvider
API Platform version(s) affected: "api-platform/core": "^3.1"
Description
By some reason, same dto inside CollectionProvider and GetProvider have different output.
I have custom resource Store:
#[ApiResource(
operations: [],
normalizationContext: [
'groups' => ['store:read'],
'skip_null_values' => false
]
)]
#[GetCollection(
provider: StoreCollectionProvider::class,
)]
#[Get(
uriTemplate: '/stores/uuid/{uuid}',
uriVariables: ['uuid'],
provider: StoreByUuidProvider::class
)]
class Store
{
// empty here
}
Both state provider's return same StoreDTO (array of DTO or single DTO):
class StoreDto
{
#[Groups(['store:read'])]
public string $uuid;
#[Groups(['store:read'])]
public function getRatings(): array
{
/**
* ratings array look like this (json encoded):
* {"googleMaps":{"aggregate":"4.4"}}
*/
return $this->ratings;
}
}
Inside StoreCollectionProvider i return array of DTO's, i get following response (notice how ratings field rendered):
{
"@context": "/api/contexts/Store",
"@id": "/api/stores",
"@type": "hydra:Collection",
"hydra:totalItems": 1,
"hydra:member": [
{
"@type": "StoreDto",
"@id": "/api/.well-known/genid/81b3efd58cff9348e116",
"uuid": "7097fc06-6796-4089-9ad2-efae128a5dda",
"ratings": {
"googleMaps": {
"aggregate": "4.4"
}
}
}
],
"hydra:view": {
"@id": "/api/stores?itemsPerPage=12",
"@type": "hydra:PartialCollectionView"
}
}
If i return same DTO inside StoreByUuidProvider, by some reason i get ratings rendered as collection:
{
"@context": {
"@vocab": "http://sharmax.karrakoliko.local/api/docs.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"uuid": "StoreDto/uuid",
"ratings": {
"@id": "StoreDto/ratings",
"@type": "@id"
}
},
"@type": "StoreDto",
"@id": "/api/.well-known/genid/19ca9bc27f5d80ba23b5",
"uuid": "7097fc06-6796-4089-9ad2-efae128a5dda",
"ratings": {
"@context": "/api/contexts/Store",
"@id": "/api/stores/uuid/7097fc06-6796-4089-9ad2-efae128a5dda",
"@type": "hydra:Collection",
"hydra:totalItems": 1,
"hydra:member": [
{
"aggregate": "4.4"
}
]
}
}
Expected
ratings field will be the rendered the same for StoreCollectionProvider and StoreByUuidProvider
In StoreByUuidProvider I expected it to be like this:
{
"ratings": {
"googleMaps": {
"aggregate": "4.4"
}
}
}
Actual
In StoreByUuidProvider I got this:
{
"ratings": {
"@context": "/api/contexts/Store",
"@id": "/api/stores/uuid/7097fc06-6796-4089-9ad2-efae128a5dda",
"@type": "hydra:Collection",
"hydra:totalItems": 1,
"hydra:member": [
{
"aggregate": "4.4"
}
]
}
}
I tried to modify #[ApiProperty] attribute over ratings (adding readableLink: false, overriding jsonLd/openapi contexts) - no luck
Can you show the provider? also why is this typed array if it returns a string?
public function getRatings(): array
Can you show the provider? also why is this typed array if it returns a string?
public function getRatings(): array
no, it returns array:
json_decode('{"googleMaps":{"aggregate":"4.4"}}',true);
Provider code is complicated, as it resolves concrete stores set from config, and then convert each of them to StoreDto.
Omitting unrelated details, it looks like this:
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if(!array_key_exists('uuid',$uriVariables)){
throw new \RuntimeException('No uuid provided');
}
$uuid = UuidV4::fromString($uriVariables['uuid']);
/** @var StoreInterface $store */
foreach ($this->stores as $store) {
if (!$store->getUuid()->equals($uuid)) {
continue;
}
$store->addRating(GoogleMapsRatingProvider::NAME, $this->googleRatingProvider->getAggregateRating($store));
break;
}
$storeDto = StoreDto::createFromStore($store);
return $storeDto;
}
Does adding output: StoreDto::class to each of the operations work?
I have rewritten code to use ApiResource directly, with no DTO use at all, and it works now as expected.
Does adding
output: StoreDto::classto each of the operations work?
it does, but then jsonLd context lost (no @id, etc):
// apiResource
#[Get(
uriTemplate: 'stores/uuid/{uuid}',
uriVariables: ['uuid'],
output: StoreDto::class,
provider: StoreByUuidProvider::class
)]
class Store {/* ... */}
response:
{
...
"ratings": {
"googleMaps": {
"aggregate": "4.4"
}
}
}
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.