2

I want to deserialize an XML file into an Entity using JMS Serializer. It works pretty well for direct properties. But when it comes to nested properties, I can't make it work without creating related entities. For example :

<idt>
    <rcs>XXXXXXX</rcs>
    <name>NAME</name>
    <main>
        <adr_1>
            <type>YYYYY</type>
            <street>YYYYYYY</street>
            <zip>XXXXX</zip>
        </adr_1>
    </main>
</idt>

I need to create an Idt entity, and the deserialization will work fine for rcs and name, but for main I have to create a Main entity with a OneToOne relation that contains a Adr1 entity that contains type, street and zip properties. This is pretty heavy. Is there any way to tell the serializer the path to hydrate a property? Something of the like :

class XmlRawExecutive
{
    /**
     * @var integer
     *
     * @ORM\Column(name="rcs", type="string", length=3, nullable=false)
     * @JMS\Type("string")
     */
    private $rcs;

    /**
     * @var integer
     *
     * @ORM\Column(name="main_adr1_street", type="integer", nullable=false)
     * @JMS\Type("string")
     */
    private $mainAdr1Street;

So I can hydrate a unique entity from the XML.

1 Answer 1

1

JMS Serializer imposes quite strict expectations on the way how deserialized data are mapped to the resulting objects. I would suggest using some more flexible library for scenarios like this.

However, I managed to deserialize the given XML to the given flat object even with JMS Serializer. The solution uses a pre_deserialize event listener, modifying the parsed XML data:

use JMS\Serializer\EventDispatcher\EventDispatcher;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\Annotation as JMS;

class Idt
{
    /**
     * @JMS\Type("string")
     */
    protected $rcs;

    /**
     * @JMS\Type("string")
     */
    protected $mainAdr1Street;
}

/**
 * turns nested elements into children of the root element
 * with names combining names of all the ancestors
 */
function recursiveChildrenInliner (\SimpleXMLElement $root, \SimpleXMLElement $child, $stack = []) {
    if ($child->count() === 0) {
        // produces element name like 'main_adr1_street'
        $name = join('_', array_map(
            function ($n) { return str_replace('_', '', $n); },
            $stack
        ));
        $root->addChild($name, $child->__toString());
    } else {
        foreach ($child->children() as $child) {
            $stackCopy = $stack;
            array_push($stackCopy, $child->getName());

            recursiveChildrenInliner(
                $root,
                $child,
                $stackCopy
            );
        }
    }
};

// build custom serializer instance with a listener registered
$serializer = SerializerBuilder::create()
    ->configureListeners(function (EventDispatcher $dispatcher) {
        $dispatcher->addListener('serializer.pre_deserialize',
            function (PreDeserializeEvent $event) {
                $data = $event->getData();

                recursiveChildrenInliner($data, $data->main, ['main']);
            }
        );
    })
    ->build();

$result = $serializer->deserialize($xml, Idt::class, 'xml');
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the answer eventhough I had to go with the subentities (the project had a short deadline). If I have time I might try to integrate this !

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.