Search code examples
phpjsonsymfonyserializationjms-serializer

How deserialize from array ids in jms?


I can not figure out how to deserialize a object from a array of IDs? Here is a simple example my array.

"roles":["1","2","3"]

But when trying I get the error that object expected-class AppBundle\Entity\Role... is There something for this? Thank you all for your attention!


Solution

  • The problem is the one mentioned by LBA in his answer. Basically in order for jms serializer to create an object, it need to be provided with an Array. The solution if you are ok with changing the structure of the api is also good.

    I'm assuming your $role jms configuration looks something like this

    /**
     * @var ArrayCollection
     *
     * @JMS\Expose
     * @JMS\Accessor(setter="setRoles")
     * @JMS\SerializedName("roles")
     * @JMS\Type("ArrayCollection<Role>")
     * @JMS\Groups({"...."})
     *
     */
    private $roles;
    

    In this case Jms will expect to get and array of arrays just like LBA mentioned. If instead you want he keep the current structure ("roles":["1","2","3"]) instead of "roles":["id": 1], ["id": 2], ["id": 3]], there is an alternative. You can extend the JsonDeserializationVisitor and this a very powerful tool in your arsenal to do almost anything you like with jms.

    To do this first change your @Type like this(you will understand why latter)

    * @JMS\Type("ArrayCollection<Role,Expand<id>>")
    

    Now you use parameter overwrite, to extend JsonDeserializationVisitor, like this

    <parameter key="jms_serializer.json_deserialization_visitor.class">MyBundle\Services\JsonDeserializationVisitor.php</parameter>
    

    And then go and define the new Visitor something like this.

    <?php
    
        namespace MyBundle\Services\JmsSerializer;
    
        use JMS\Serializer\Context;
        use JMS\Serializer\JsonDeserializationVisitor as ParentJsonDeserializationVisitor;
        use JMS\Serializer\Metadata\ClassMetadata;
        use JMS\Serializer\Metadata\PropertyMetadata;
    
        class JsonDeserializationVisitor extends ParentJsonDeserializationVisitor
        {
            /**
             * @param PropertyMetadata $metadata
             * @param mixed $data
             * @param Context $context
             */
            public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
            {
                //This type is the information you put into the @Type annotation. 
                $type = $metadata->type;
    
                $expand = null;
                .......
        /*Here you can extract the Expand<id> part that you added to the @Type config. 
    The Expand part will help you identify the fact that this property needs to be expanded and the id part will tell you how to expand id.
    Based on if you do find this you will probably set a variable like $expand = $key, where $key is the "id" part.*/
                ......
                if ($expand !== null) {
                    $expandedData = [];
                    foreach($data as $key->$value)
                    {
                        $expandedData[$key]=["$expand":$value];
                    }
                    parent::visitProperty($metadata, $expandedData, $context);
                } else {
                    parent::visitProperty($metadata, $data, $context);
                }
    
            }
    

    This is the basic stuff. Feel free to refactor the code however you like, this is just for fast proof of concept. Also Expand is just how i named this into the exemple. You can use it with whatever name you like. Sorry for not providing the code to extract this from $type but i don't remember the structure of $type right now. Also the structure will change if you do something like Expand<'id'> so play with it and see which fits best for you. Basically with this method you can extend and add features to any jms type you want. For instance you can add something like

     @JMS\Type("string<translatable>")
    

    And then extend JsonSerializationVisitor:visitString to call $translator->trans($data) on $data before returning it, and so you can translate a string before serializing it.

    Something like this

    <?php
    
    namespace MyBundle\Service\JmsSerializer;
    
    use JMS\Serializer\Context;
    use JMS\Serializer\JsonSerializationVisitor as ParentJsonSerializationVisitor;
    use Symfony\Component\Translation\Translator;
    
    class JsonSerializationVisitor extends ParentJsonSerializationVisitor
    {
        /**
         * @var Translator;
         */
        private $translator;
    
        const TRANSLATABLE = "translatable";
    
        /**
         * @param string $data
         * @param array $type
         * @param Context $context
         * @return string
         */
        public function visitString($data, array $type, Context $context)
        {
            $translatable = $this->getParameters(self::TRANSLATABLE, $type['params']);
            if ($translatable) {
                $data = (string)$this->translator->trans($data);
            }
            return parent::visitString($data, $type, $context);
        }
        .....................
    

    Hope this helps. Let me know if you have any questions.

    Alexandru Cosoi