Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize null values as empty string #566

Closed
Seb33300 opened this issue Mar 31, 2016 · 8 comments
Closed

Serialize null values as empty string #566

Seb33300 opened this issue Mar 31, 2016 · 8 comments

Comments

@Seb33300
Copy link

Hi,

I would like to force the serialization of null values as empty strings '' instead of null.

I set the option setSerializeNull(true) to force serialization of null values, but how to transform them into empty string?
Maybe using the post serialize event?
But I do not understand how to use the ObjectEvent object. No sample is given.

Thanks

@Seb33300 Seb33300 changed the title Serializer null values as empty string Serialize null values as empty string Mar 31, 2016
@Seb33300
Copy link
Author

Seb33300 commented Mar 31, 2016

Just solved my problem using a custom visitor:

<?php

namespace Project\Namespace\Serializer;

use JMS\Serializer\Context;
use JMS\Serializer\JsonSerializationVisitor;

class BlankSerializationVisitor extends JsonSerializationVisitor
{
    /**
     * {@inheritdoc}
     */
    public function visitNull($data, array $type, Context $context)
    {
        return '';
    }
}

And then, set it to your serializer using the setSerializationVisitor method.

@matuskopo
Copy link

Hi,

we wanted to achieve the same, but just for string types. So we override visitString method, but it's not working.
Any help?

Bye

@goetas
Copy link
Collaborator

goetas commented Jan 18, 2018

Use a "custom/virtual" type for the string and implement a handler for it.
Advantages:

  • clean approach
  • selective (you can have still strings that are serialized to null)

Disadvantages:

  • you will need to declare the @Type on the properties that should be serialized as ""

@matuskopo
Copy link

Oh, thank you a lot. I've missed this somehow in docs. Bye

@matuskopo
Copy link

So, I did everything you said, but custom handler is called only if value is not null. If string is null, we can't change it even with custom type and custom handler.

@goetas
Copy link
Collaborator

goetas commented Jan 18, 2018

did you enable serializeNulls in the context?

@matuskopo
Copy link

matuskopo commented Jan 19, 2018

sure. I've set it in config.yml like this:

jms_serializer:
    default_context:
        serialization:
            serialize_null: true

and this too:

fos_rest:
    serializer:
        serialize_null: true

@jdiazgon55
Copy link

It seems the way @Seb33300 mentions is now not valid, as JsonSerializationVisitor is a final class (you cannot extend it). What I had to do is:

  1. Create new class BlankSerializationVisitor wherever you want.
  2. Copy and paste class contents JsonSerializationVisitor into it and set visitNull to return ''.
<?php

declare(strict_types=1);

namespace App\Serializer;

use JMS\Serializer\Exception\NotAcceptableException;
use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Visitor\SerializationVisitorInterface;
use JMS\Serializer\AbstractVisitor;

final class BlankSerializationVisitor extends AbstractVisitor implements SerializationVisitorInterface
{
    /**
     * @var int
     */
    private $options;

    /**
     * @var array
     */
    private $dataStack;
    /**
     * @var \ArrayObject
     */
    private $data;

    public function __construct(
        int $options = JSON_PRESERVE_ZERO_FRACTION
    ) {
        $this->dataStack = [];
        $this->options = $options;
    }

    /**
     * {@inheritdoc}
     */
    public function visitNull($data, array $type)
    {
        return '';
    }

    /**
     * {@inheritdoc}
     */
    public function visitString(string $data, array $type)
    {
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function visitBoolean(bool $data, array $type)
    {
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function visitInteger(int $data, array $type)
    {
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function visitDouble(float $data, array $type)
    {
        return $data;
    }

    /**
     * @param array $data
     * @param array $type
     *
     * @return array|\ArrayObject
     */
    public function visitArray(array $data, array $type)
    {
        \array_push($this->dataStack, $data);

        $rs = isset($type['params'][1]) ? new \ArrayObject() : [];

        $isList = isset($type['params'][0]) && !isset($type['params'][1]);

        $elType = $this->getElementType($type);
        foreach ($data as $k => $v) {
            try {
                $v = $this->navigator->accept($v, $elType);
            } catch (NotAcceptableException $e) {
                continue;
            }

            if ($isList) {
                $rs[] = $v;
            } else {
                $rs[$k] = $v;
            }
        }

        \array_pop($this->dataStack);

        return $rs;
    }

    public function startVisitingObject(ClassMetadata $metadata, object $data, array $type): void
    {
        \array_push($this->dataStack, $this->data);
        $this->data = true === $metadata->isMap ? new \ArrayObject() : [];
    }

    /**
     * @return array|\ArrayObject
     */
    public function endVisitingObject(ClassMetadata $metadata, object $data, array $type)
    {
        $rs = $this->data;
        $this->data = \array_pop($this->dataStack);

        if (true !== $metadata->isList && empty($rs)) {
            return new \ArrayObject();
        }

        return $rs;
    }

    /**
     * {@inheritdoc}
     */
    public function visitProperty(PropertyMetadata $metadata, $v): void
    {
        try {
            $v = $this->navigator->accept($v, $metadata->type);
        } catch (NotAcceptableException $e) {
            return;
        }

        if (true === $metadata->skipWhenEmpty && ($v instanceof \ArrayObject || \is_array($v)) && 0 === count($v)) {
            return;
        }

        if ($metadata->inline) {
            if (\is_array($v) || ($v instanceof \ArrayObject)) {
                // concatenate the two array-like structures
                // is there anything faster?
                foreach ($v as $key => $value) {
                    $this->data[$key] = $value;
                }
            }
        } else {
            $this->data[$metadata->serializedName] = $v;
        }
    }

    /**
     * Checks if some data key exists.
     */
    public function hasData(string $key): bool
    {
        return isset($this->data[$key]);
    }

    /**
     * @deprecated Use `::visitProperty(new StaticPropertyMetadata('', 'name', 'value'), 'value')` instead
     *
     * Allows you to replace existing data on the current object element.
     *
     * @param mixed $value This value must either be a regular scalar, or an array.
     *                                                       It must not contain any objects anymore.
     */
    public function setData(string $key, $value): void
    {
        $this->data[$key] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function getResult($data)
    {
        $result = @json_encode($data, $this->options);

        switch (json_last_error()) {
            case JSON_ERROR_NONE:
                return $result;

            case JSON_ERROR_UTF8:
                throw new RuntimeException('Your data could not be encoded because it contains invalid UTF8 characters.');

            default:
                throw new RuntimeException(sprintf('An error occurred while encoding your data (error code %d).', json_last_error()));
        }
    }
}
  1. Create new class BlankSerializationVisitorFactory that uses your BlankSerializationVisitor
<?php

namespace App\Serializer;

use JMS\Serializer\Visitor\SerializationVisitorInterface;
use JMS\Serializer\Visitor\Factory\SerializationVisitorFactory;

/**
 * @author Asmir Mustafic <[email protected]>
 */
final class BlankSerializationVisitorFactory implements SerializationVisitorFactory
{
    /**
     * @var int
     */
    private $options = JSON_PRESERVE_ZERO_FRACTION;

    public function getVisitor(): SerializationVisitorInterface
    {
        return new BlankSerializationVisitor($this->options);
    }

    public function setOptions(int $options): self
    {
        $this->options = $options;

        return $this;
    }
}
  1. Finally, force your serializer to use that SerializationVisitorFactory:
$serializer = SerializerBuilder::create()->setSerializationVisitor('json', new BlankSerializationVisitorFactory())->build();

I don't know if there is an easier way, but it worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants