Custom serialized value for boolean properties on Symfony Serializer

1.4k views Asked by At

I'm using the Symfony Serializer 3.3 bundle to convert and object to XML.

And I want boolean type returned as Y or N, instead of 1 or 0, and I don't want to change the accessor method.

Here's an example:

namespace Acme;

class Person
{
    private $name;
    private $enabled;

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

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

    public function isEnabled()
    {
        return $this->enabled;
    }

    public function setEnabled($enabled)
    {
        $this->enabled = $enabled;
    }
}

$person = new Acme\Person();
$person->setName('foo');
$person->setEnabled(true);

$serializer->serialize($person, 'xml');

getting result:

<?xml version="1.0"?>
<response>
    <name>foo</name>
    <enabled>1</enabled> <!-- bad value -->
</response>

desired result:

<?xml version="1.0"?>
<response>
    <name>foo</name>
    <enabled>Y</enabled>  <!-- goodvalue -->
</response>
2

There are 2 answers

2
Artem On

You can do this by event subscriber. It affects all boolean properties

<?php

declare(strict_types=1);

namespace App\EventListener\Serializer\Entity;

use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\Metadata\StaticPropertyMetadata;
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Encoder\XmlEncoder;

class BoolSubscriber implements EventSubscriberInterface
{
    /**
     * @return array<array<string, mixed>>
     */
    public static function getSubscribedEvents(): array
    {
        return [
            [
                'event'    => Events::POST_SERIALIZE,
                'method'   => 'onPostSerialize',
                'format'   => XmlEncoder::FORMAT,
                'priority' => 0,
            ],
        ];
    }

    public function onPostSerialize(ObjectEvent $event): void
    {
        $visitor = $event->getVisitor();

        $class = get_class($event->getObject());
        $reflectionExtractor = new ReflectionExtractor();
        $properties          = $reflectionExtractor->getProperties($class);
        $propertyAccessor = new PropertyAccessor();

        foreach ($properties as $property) {
            $types = $reflectionExtractor->getTypes($class, $property);
            $type = $types[0] ?? null;

            if ($type instanceof Type && $type->getBuiltinType() == Type::BUILTIN_TYPE_BOOL) {
                $metadata = new VirtualPropertyMetadata($class, $property);

                if ($visitor->hasData($metadata->name)) {
                    $value = $propertyAccessor->getValue($event->getObject(), $property) ? 'Y' : 'N';
                    $visitor->visitProperty(
                        new StaticPropertyMetadata($class, $metadata->name, $value),
                        $value
                    );
                }
            }
        }
    }
}
0
Artem On

You can register a new jms type formatted_boolean

<?php

declare(strict_types=1);

namespace App\Util\Serializer\Normalizer;

use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\XmlSerializationVisitor;
use Symfony\Component\Serializer\Encoder\XmlEncoder;

class BoolHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods(): array
    {
        return [
            [
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
                'format'    => XmlEncoder::FORMAT,
                'type'      => 'formatted_boolean',
                'method'    => 'serializeToXml',
            ],
        ];
    }

    public function serializeToXml(
        XmlSerializationVisitor $visitor,
        $value,
        array $type,
        Context $context = null
    ) {

        return $value ? 'Y' : 'N';
    }
}

But in this case, you have to add @JMS\Type(name="formatted_boolean") for each boolean property