3

The Symfony 3.4 documentation states the following for deserializing arrays:

use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;

$serializer = new Serializer(
array(new GetSetMethodNormalizer(), new ArrayDenormalizer()),
array(new JsonEncoder())
);

$data = ...; // The serialized data from the previous example
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');

The json string is the following:

[{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}]

So I tried to do the same with my XML structure. It's not a real structure as I'm testing the thing.

XML Structure:

<<<EOF
<response>
 <book>
  <titulo>foo</titulo>
  <isbn>99</isbn>
  <autor>Autor</autor>
  <editor>Editor</editor>
 </book>
 <book>
  <titulo>foo2</titulo>
  <isbn>100</isbn>
  <autor>Autor2</autor>
  <editor>Editor2</editor>
 </book>
</response>
EOF;

Response is the default root node name. I have a Book entity with the fields defined identically. I try to deserialize like that:

use AppBundle\Entity\Book;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;

$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new GetSetMethodNormalizer(), new ArrayDenormalizer());
$serializer = new Serializer($normalizers, $encoders);

 $serializer->deserialize($data,  'AppBundle\Entity\Book[]', 'xml');

When I do a var_dump of the deserialize variable, the output is the following:

array(1) { ["book"]=> object(AppBundle\Entity\Book)#385 (11) { ["isbn":protected]=> NULL ["autor":protected]=> NULL ["titulo":protected]=> NULL ["fecha_ini":protected]=> NULL ["fecha_fin":protected]=> NULL ["editor":protected]=> NULL ["imgUrl":protected]=> NULL ["cod_autor":protected]=> NULL ["cod_editorial":protected]=> NULL ["cod_coleccion":protected]=> NULL ["cod_mat":protected]=> NULL } }

Data are not recognized and the array has only one element when I expect 2 elements.

Does someone experience something like that? Can you help me where to look for the solution ?

Thanks in advance.

2
  • can you share $data? and try removing <response>? Commented Mar 2, 2018 at 15:31
  • $data is the XML structure above. If I delete <response>, I have to replace it by another root node. Which one? The xml structure can't be valid without it. Commented Mar 3, 2018 at 9:27

1 Answer 1

3

XML is not JSON and the root element is not "just a wrapper for an array", so you have to pay it a due respect. There are two ways to approach this:

1. Introduce a deserialization model for the root element - something like

class Response
{
    /**
     * @var Book[]
     */
    protected $book;

    /**
     * @return Book[]
     */
    public function getBook(): array
    {
        return $this->book;
    }

    /**
     * @param Book[] $book
     */
    public function setBook(array $book): void
    {
        $this->book = $book;
    }
}

and then access books like

$response = $serializer->deserialize($xml,  'App\Entity\Response', 'xml');
$books = $response->getBook();

In this case, however, your simple serializer configuration won't suffice - in order to get books properly deserialized as Book instances it is necessary to add an additional bit of functionality to extract type information for the nested entities:

$encoders = array(new XmlEncoder());
$normalizers = array(new GetSetMethodNormalizer(null, null, new PhpDocExtractor()), new ArrayDenormalizer()); // <- PhpDocExtractor
$serializer = new Serializer($normalizers, $encoders);

PhpDocExtractor extracts type information from PhpDoc comments.

2. Introduce a custom denormalizer

Alternatively you can hook directly into the deserialization process with a custom denormalizer

use App\Entity\Book;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;

class BookArrayDenormalizer extends ArrayDenormalizer
{
    public function supportsDenormalization($data, $type, $format = null, array $context = [])
    {
        // only support deserialization of Book[]
        return Book::class.'[]' === $type;
    }

    public function denormalize($data, $class, $format = null, array $context = [])
    {
        return parent::denormalize(
            $data['book'], // this is the magic to ignore the root element
            $class, $format, $context
        );
    }
}

And enjoy deserializing an array of Books without a wrapping object:

$normalizers = array(new GetSetMethodNormalizer(), new BookArrayDenormalizer(), new ArrayDenormalizer()); // add the new denormalizer
$encoders = array(new XmlEncoder());
$serializer = new Serializer($normalizers, $encoders);

$books = $serializer->deserialize($xml,  'App\Entity\Book[]', 'xml');
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.