schmittjoh / serializer

Library for (de-)serializing data of any complexity (supports JSON, and XML)
http://jmsyst.com/libs/serializer
MIT License
2.32k stars 585 forks source link

Serializing ArrayCollection of an abstract class returns empty objects #411

Open andreaslarssen opened 9 years ago

andreaslarssen commented 9 years ago

I'm using the Serlalizer Bundle with Symfony2, but suspect this is a Serializer issue.

I have an abstract class "Question":

/**
 * App\SurveyBundle\Entity\Question
 * 
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="integer")
 * @ORM\DiscriminatorMap({0 = "App\SurveyBundle\Entity\Question", 3 = "App\SurveyBundle\Entity\MultipleChoice", 2 = "App\SurveyBundle\Entity\SingleChoice", 1 = "App\SurveyBundle\Entity\LikertScale"})
 * @JMS\Discriminator(field="type", map={0: "App\SurveyBundle\Entity\Question", 3: "App\SurveyBundle\Entity\MultipleChoice", 2: "App\SurveyBundle\Entity\SingleChoice", 1: "App\SurveyBundle\Entity\LikertScale"})
 * @ORM\HasLifeCycleCallbacks
 */
abstract class Question {
    /**
     * Question type
     */
    public function getQuestionType() {
        if ($this instanceof MultipleChoice) {
            return 3;
        } else if($this instanceof SingleChoice) {
            return 2;
        } else if($this instanceof LikertScale) {
            return 1;
        } else {
            return 0;
        }
    } 
}

...extended by three sub classes, using this yml annotation file (Example for the "SingleChoice" extended class).

App\SurveyBundle\Entity\SingleChoice:
    exclusion_policy: ALL
    properties:
        id:
            expose: true
            type: integer
            groups: [default]
        questionText:
            expose: true
            serialized_name: questionText
            type: string
            groups: [default]
        created:
            expose: true
            type: DateTime
            groups: [default]
        survey:
            expose: true
            type: App\SurveyBundle\Entity\Survey
            groups: [deserialization]
    virtual_properties:
        getQuestionType:
            serialized_name: type
            type: integer
            groups: [default]

The abstract class "Question" is used by my "Survey" class, which holds an ArrayCollection of Questions.

App\SurveyBundle\Entity\Survey:
    exclusion_policy: ALL
    properties:
        id:
            expose: true
            type: integer
            groups: [default]
        created:
            expose: true
            type: DateTime
            groups: [default]
        name:
            expose: true
            type: string
            groups: [default]
        questions:
            expose: true
            type: ArrayCollection<App\SurveyBundle\Entity\Question>
            groups: [default]

When serializing a survey

$serializer->serialize($survey, 'json', SerializationContext::create()->enableMaxDepthChecks()->setGroups(array('default'));

...this is the output


{
    id: 1,
    created: "2015-03-02T10:48:53+0100",
    name: "test",
    questions: [
        { },
        { },
        { },
        { },
        { },
        { },
        { }
    ]
}

Are anyone else seeing this kind of behavior? Am I doing something wrong?

erichjsonfosse commented 9 years ago

+1

wdalmut commented 9 years ago

+1

nico-incubiq commented 9 years ago

+1

nico-incubiq commented 9 years ago

Still no update on that issue? For my part, if I add some fields on your "Question" entity, they will appear in the serialized content, but the ones from subclasses remain hidden. Removing the "@Type" annotation solves the issue but I do not think this is what we want.

In GraphNavigator, changing line 93 to this fixes it too (Still ugly because it does not use Discriminator Map):

if (null === $type OR ('object' === gettype($data) AND 'array' !== $type['name'] AND (new \ReflectionClass($type['name']))->isAbstract())) {

Or before "default" in switch line 282 you can enable loading discriminator value from VirtualProperty:

case is_object($data)
        && !empty($metadata->propertyMetadata[$metadata->discriminatorFieldName])
        && $metadata->propertyMetadata[$metadata->discriminatorFieldName] instanceof VirtualPropertyMetadata:
$typeValue = (string) $data->{$metadata->propertyMetadata[$metadata->discriminatorFieldName]->getter}();
break;

nb: in this same switch you can notice that without this fix the discriminator has to be a "public" property of the class, since accessors are not used... Even though a "public" property would not work either.