symfonyjmsserializerbundle

How to create a JMSSerialization Handler for for base types, e.g. array <-> csv string


How to create a custom JMSSerializen handler for base types like strings or arrays? The goal would be to (de)serialize some properties in non-default ways. For example to specify that one array property should be deserialized to a CSV string instead to a default JSON array or one string string property to to an encrypted / encoded string, etc.

While creating such a handler for a custom class was no problem, I struggle to do the same for base types.

class SyncableEntityHandler implements SubscribingHandlerInterface {

    public static function getSubscribingMethods() {
        return [
            [
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'SomeClass',
                'method' => 'serializeSomeClassToJson',
            ],
            [
                'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
                'format' => 'json',
                'type' => 'SomeClass',
                'method' => 'deserializeSomeClassFromJson',
            ],
        ];
    }

    public function serializeSomeClassToJson(JsonSerializationVisitor $visitor, AbstractSyncableEntity $entity, array $type, Context $context) {
        ...
    }

    public function deserializeSomeClassFromJson(JsonDeserializationVisitor $visitor, $entityGuid, array $type, Context $context) {
        ...
    }    
}


class OtherClass {
    /*
     * @JMS\Type("SomeClass")
     */
    protected $someProperty;

     /*
     * @JMS\Type("array")
     */
    protected $serializeToDefaultArray;

     /*
     * @JMS\Type("csv_array")    // How to do this?
     */
    protected $serializeToCSVString;
}

While I can create a handler with 'type' => 'array' to change the (de)serializiation of all arrays, I do not know to only select some arrays.

I already thought about using getters and setters instead (e.g. getPropertyAsCsvString() and setPropertyFromCsvString()). While this would work with custom classes, I would like to serialize some third-party classes as well where I specify the serialization options not with annotations but in .yaml files. Adding getter and setters to these classes is not (easily) possible. Additionally creating these getters and setters would add a lot of overhead.

So, is there a way to create and specify special handlers for some properties?


Solution

  • The implementation is quite straightforward:

    class CsvArrayHandler implements SubscribingHandlerInterface {
        public static function getSubscribingMethods() {
            return [
                [
                    'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                    'format' => 'json',
                    'type' => 'csv_array',
                    'method' => 'serializeToJson',
                ],
                [
                    'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
                    'format' => 'json',
                    'type' => 'csv_array',
                    'method' => 'deserializeFromJson',
                ],
            ];
        }
    
        public function serializeSomeClassToJson(JsonSerializationVisitor $visitor, array $array, array $type, Context $context) {
            return implode(',', $array);
        }
    
        public function deserializeSomeClassFromJson(JsonDeserializationVisitor $visitor, string $csvString, array $type, Context $context) {
            return explode(',', $csvString);
        }
    }
    

    Then just annotate your property with @JMS\Type("csv_array") and register the handler.

    Note that using explode and implode does not escape the input so you may want to use something like league/csv instead.