Deserializing collections in silex

By Jean Hertel, 06/04/2017, in category Tips

collections, Serializer, Silex, Symfony

Today I had to transform a complex JSON into objects and suffered enough to figure out the solution. The JSON in question was something like this:

{
    "name": "author name",
    "posts": [
        {
            "title": "some post 1"
        },
        {
            "title": "some post 2"
        }
    ]
}

The classes in turn:

<?php

class Author
{
    private $name;
    private $posts;

    public function __construct()
    {
        $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getPosts()
    {
        return $this->posts;
    }

    public function setPosts(array $posts)
    {

        $this->posts->clear();

        foreach ($posts as $post) {
            $this->addPosts($post);
        }
    }

    public function addPosts(Post $post)
    {
        $this->posts->add($post);
    }

    public function removePosts(Post $post)
    {
        $this->posts->removeElement($post);
    }
}

class Post
{
    private $title;

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

    public function setTitle($title)
    {
        $this->title = $title;
    }
}

But how do we do that? If you have not read my post on how to configure the serializer, read it here.

If you configured it as I did in the other post, only get and set will be used to load the properties of the classes. To be able to deserialize arrays, you must configure a Symfony\Component\Serializer\Normalizer\ArrayDenormalizer.

But that's not enough, because the collection will be deserialized and passed as an array. To properly detect our object, we need to tell the serializer its type. This can be done by configuring a Symfony\Component\Serializer\Normalizer\ObjectNormalizer with a Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor.

One quick way to do this is to take the example from the other page and change it to look like this:

<?php
$app['serializer.normalizers'] = function () use ($app) {
    $propertyInfo = new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor();

    return [
        new \Symfony\Component\Serializer\Normalizer\ArrayDenormalizer(),
        new \Symfony\Component\Serializer\Normalizer\ObjectNormalizer(null, null, null, $propertyInfo)
    ];
};

The ObjectNormalizer class reads the object properties and calls its getters and setters. With the help of the ReflectionExtractor class it can know the types of objects. But how are types discovered? The important thing here is to have the methods addPosts and removePosts receiving as parameter the object with its correct type hint. This way the extractor can identify which object should deserialize.

A complete and functional example is available in this repository.

One last detail: the ReflectionExtractor class requires symfony/property-info and symfony/property-access dependencies.