question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Generation doc for nested DTO objects

See original GitHub issue

Hello. I am using DTO for GET methods. All work fine except documentation. I have 3 levels of nested objects (relations). And for nested objects in swagger describe like simple string. For example I have 3 DTO:


declare(strict_types=1);

namespace App\API\Resource\DTO\Quiz;

use ApiPlatform\Core\Annotation\ApiProperty;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

final class QuizStepDTO
{
    /**
     * @Groups({"quizStep:read"})
     * @Assert\NotNull()
     *
     * @var int
     */
    private $id;

    /**
     * @Groups({"quizStep:read"})
     * @Assert\NotNull()
     *
     * @var string
     */
    private $title;

    /**
     * @Groups({"quizStep:read"})
     * @Assert\NotNull()
     *
     * @var QuizStepChoiceDTO[]
     */
    private $quizStepChoices;


    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @param int $id
     * @return QuizStepDTO
     */
    public function setId(int $id): QuizStepDTO
    {
        $this->id = $id;
        return $this;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    /**
     * @param string $title
     * @return QuizStepDTO
     */
    public function setTitle(string $title): QuizStepDTO
    {
        $this->title = $title;
        return $this;
    }


    public function getQuizStepChoices(): array
    {
        return $this->quizStepChoices;
    }

    /**
     * @param QuizStepChoiceDTO[] $quizStepChoices
     * @return QuizStepDTO
     */
    public function setQuizStepChoices(array $quizStepChoices): QuizStepDTO
    {
        $this->quizStepChoices = $quizStepChoices;
        return $this;
    }
}```

 QuizStepChoiceDTO definition:

```php
namespace App\API\Resource\DTO\Quiz;

use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

final class QuizStepChoiceDTO
{
    /**
     * @Groups({"quizStep:read"})
     * @Assert\NotNull()
     *
     * @var int
     */
    private $position;

    /**
     * @Groups({"quizStep:read"})
     * @Assert\NotNull()
     *
     * @var ChoiceDTO
     */
    private $choice;

    public function getPosition(): int
    {
        return $this->position;
    }

    /**
     * @param int $position
     * @return QuizStepChoiceDTO
     */
    public function setPosition(int $position): QuizStepChoiceDTO
    {
        $this->position = $position;
        return $this;
    }

    public function getChoice(): ChoiceDTO
    {
        return $this->choice;
    }

    /**
     * @param ChoiceDTO $choice
     * @return QuizStepChoiceDTO
     */
    public function setChoice(ChoiceDTO $choice): QuizStepChoiceDTO
    {
        $this->choice = $choice;
        return $this;
    }

and ChoiceDTO

namespace App\API\Resource\DTO\Quiz;

use ApiPlatform\Core\Annotation\ApiProperty;
use App\API\Resource\DTO\Meal\IngredientAliasDTO;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

final class ChoiceDTO
{
    /**
     * @Groups({"choice:read", "quizStep:read"})
     * @Assert\NotNull()
     *
     * @param int
     */
    private $id;

    /**
     * @Groups({"choice:read", "quizStep:read"})
     * @Assert\NotNull()
     *
     * @param string
     */
    private $title;

    /**
     *
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={
     *            "type"="integer",
     *            "enum" = {1,2},
     *          }
     *     }
     * )
     *
     * @var int|null
     */
    private $measurementUnit;

    /**
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={
     *            "type"="object",
     *            "required" = {"id","name"},
     *            "properties"={
     *               "id"={"type":"integer"},
     *               "name"={"type":"string"}
     *             }
     *          }
     *     }
     * )
     *
     *
     * @var IngredientAliasDTO|null
     */
    private $ingredientAlias;
    /**
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @var string|null
     */
    private $value;

    /**
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @var int|null
     */
    private $rangeStart;

    /**
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @var int|null
     */
    private $rangeEnd;

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id): ChoiceDTO
    {
        $this->id = $id;

        return $this;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle(string $title): ChoiceDTO
    {
        $this->title = $title;

        return $this;
    }

    public function getMeasurementUnit(): ?int
    {
        return $this->measurementUnit;
    }

    public function setMeasurementUnit(?int $measurementUnit): ChoiceDTO
    {
        $this->measurementUnit = $measurementUnit;

        return $this;
    }

    public function getIngredientAlias(): ?IngredientAliasDTO
    {
        return $this->ingredientAlias;
    }

    public function setIngredientAlias(?IngredientAliasDTO $ingredientAlias): ChoiceDTO
    {
        $this->ingredientAlias = $ingredientAlias;

        return $this;
    }

    public function getValue(): ?string
    {
        return $this->value;
    }

    public function setValue(?string $value): ChoiceDTO
    {
        $this->value = $value;

        return $this;
    }

    public function getRangeStart(): ?int
    {
        return $this->rangeStart;
    }

    public function setRangeStart(?int $rangeStart): ChoiceDTO
    {
        $this->rangeStart = $rangeStart;

        return $this;
    }

    public function getRangeEnd(): ?int
    {
        return $this->rangeEnd;
    }

    public function setRangeEnd(?int $rangeEnd): ChoiceDTO
    {
        $this->rangeEnd = $rangeEnd;

        return $this;
    }

How I can describe nested DTO ?. I have tried to use a swagger_context but it not useful for those kinds of nested DTO. Are any possibility to use references $ref like in Swagger or something similar ?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:20 (2 by maintainers)

github_iconTop GitHub Comments

13reactions
mindaugasbarysascommented, Jul 29, 2019

Yes! You just need to dive into the source itself: at api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:685 you’ll find that if true === $readableLink, a $ref will be generated. $type comes from @var annotation. So to get it into documentation you need to:

    /**
     * @Groups({"choice:read", "quizStep:read"})
     *
     * @ApiProperty(readableLink=true)
     *
     * @var \Full\Namespace\To\DTO\IngredientAliasDTO
     */
    private $ingredientAlias;

and you should be good.

It is definitely a sign of good documentation is when someone has to go and read the source code in order to do somewhat trivial things. /s

Sorry about that last snark, but I just spent 4 hours trying to figure out the solution to the same problem.

6reactions
embargo2710commented, Jan 16, 2020

It should have been fixed by api-platform/core#3309 (You need to use version 2.5.x-dev of api-platform/core for now).

Please let us know if you’re still having any issues with this.

@teohhanhui Hi. I have the same issue with api-platform/core v2.5.3 and v2.5.4.

In my case: OfferSearchOutput (DTO, not resource) defined as output for custom operation. I have some nested DTO’s.

itemOperations:
        searchByTerm:
            output: App\DTO\OfferSearchOutput

OfferSearchOutput has field like this with get/set methods:

 /**
  * @var OfferOutput[]
  */
 private array $offers;

and OfferOutput with some not important fields (in fact with another compex DTO’s).

As result i have this generated docs, field offers as array of strings instead of array of my DTO:

{
"offers": [
     "string"
   ]
}

But when i tested API - all denormalization process works fine.

Can you help me?

UPDATED

I found a problem in ApiPlatform\Core\JsonSchema\TypeFactory line 102.

if ($this->isResourceClass($className) && true !== $readableLink) always true for non-resource DTO.

Next to ApiPlatform\Core\Util\ResourceClassInfoTrait line 69 - // assume that it's a resource class but it doesn’t because TypeFactory has $resourceClassResolver = null in constructor.

See ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer constructor with TypeFactory creation without params.

Read more comments on GitHub >

github_iconTop Results From Across the Web

java - How to convert nested objects into nested DTOs using ...
I am facing issue when I am trying to use ModelMapper to convert nested java objects into nested DTO's. Getting null for child...
Read more >
DTO Generator - JPA Buddy
DTO (data transfer object) is an object that carries data between processes. ... JPA Buddy offers DTO generation from JPA entities via visual...
Read more >
Create Data Transfer Objects (DTOs) - Microsoft Learn
Describes how to create data transfer objects (DTOs) manually using code to change ... Flatten object graphs that contain nested objects, ...
Read more >
Customization - OpenAPI Generator
The empty object definition following AUTHORS.md allows the tool to infer the target output filename in the root of the output directory.
Read more >
Automatically Mapping DTO to Entity on Spring Boot APIs
DTO, which stands for Data Transfer Object, is a design pattern conceived to reduce the number of calls when working with remote interfaces....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found