diff --git a/src/Bundle/Resources/config/services.xml b/src/Bundle/Resources/config/services.xml
index 5cbe5dc..ed7726e 100644
--- a/src/Bundle/Resources/config/services.xml
+++ b/src/Bundle/Resources/config/services.xml
@@ -12,6 +12,8 @@
+
+
@@ -29,6 +31,7 @@
+
diff --git a/src/Normalizer/TraversableNormalizer.php b/src/Normalizer/TraversableNormalizer.php
new file mode 100644
index 0000000..7332f2c
--- /dev/null
+++ b/src/Normalizer/TraversableNormalizer.php
@@ -0,0 +1,52 @@
+ true,
+ ];
+ }
+
+ public function normalize(mixed $object, ?string $format = null, array $context = []): array
+ {
+ $result = [];
+ foreach (iterator_to_array($object) as $key => $item) {
+ $result[$key] = $this->normalizer->normalize($item, $format, $context);
+ }
+ return $result;
+ }
+
+ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
+ {
+ return $data instanceof Traversable;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): object
+ {
+ $result = [];
+ foreach ($data as $key => $item) {
+ $result[$key] = $this->denormalizer->denormalize($item, $type, $format, $context);
+ }
+ return new $type($result);
+ }
+
+ public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
+ {
+ return is_array($data) && is_subclass_of($type, Traversable::class);
+ }
+}
diff --git a/src/Serializer.php b/src/Serializer.php
index c820c5c..15f3477 100644
--- a/src/Serializer.php
+++ b/src/Serializer.php
@@ -18,6 +18,7 @@ final class Serializer extends BaseSerializer
{
use SerializerTrait;
+ private const CONTEXT_SERIALIZER = '#serializer';
private const KEY_TYPE = '#type';
private const KEY_SCALAR = '#scalar';
}
@@ -27,6 +28,7 @@ final class Serializer extends BaseSerializer
{
use TypedSerializerTrait;
+ private const CONTEXT_SERIALIZER = '#serializer';
private const KEY_TYPE = '#type';
private const KEY_SCALAR = '#scalar';
}
diff --git a/src/SerializerTrait.php b/src/SerializerTrait.php
index 8c98237..15e8484 100644
--- a/src/SerializerTrait.php
+++ b/src/SerializerTrait.php
@@ -41,53 +41,77 @@ public function __construct(array $normalizers = [], array $encoders = [], ?Type
* @param mixed $data
* @param string|null $format
*
- * @return array|\ArrayObject|bool|float|int|string|null
+ * @return array|\ArrayObject|scalar|null
*/
public function normalize($data, $format = null, array $context = [])
{
- $normalizedData = parent::normalize($data, $format, $context);
+ if (\is_array($data)) {
+ $normData = [];
+ foreach ($data as $idx => $datum) {
+ $normData[$idx] = $this->normalize($datum, $format, $context);
+ }
+
+ return $normData;
+ }
if (\is_object($data)) {
$typeName = \get_class($data);
+ $normData = parent::normalize($data, $format, $context + [self::CONTEXT_SERIALIZER => $this]);
+
if ($this->typeMapper) {
$typeName = $this->typeMapper->getTypeByClass($typeName);
}
$typeData = [self::KEY_TYPE => $typeName];
- $valueData = is_scalar($normalizedData) ? [self::KEY_SCALAR => $normalizedData] : $normalizedData;
- $normalizedData = array_merge($typeData, $valueData);
+
+ if (\is_array($normData) && !isset($normData[self::KEY_TYPE])) {
+ $normData = $this->normalize($normData, $format, $context);
+ }
+ if (\is_scalar($normData)) {
+ $normData = [self::KEY_SCALAR => $normData];
+ }
+
+ return \array_merge($typeData, $normData);
}
- return $normalizedData;
+ return $data;
}
/**
- * @param $data
+ * @param null|scalar|array $data
+ * @param string $type
+ * @param string|null $format
*
* @return mixed
*/
public function denormalize($data, $type, $format = null, array $context = [])
{
- if (\is_array($data) && (isset($data[self::KEY_TYPE]))) {
+ if (!\is_array($data)) {
+ return $data;
+ }
+
+ if (isset($data[self::KEY_TYPE])) {
$keyType = $data[self::KEY_TYPE];
+ unset($data[self::KEY_TYPE]);
if ($this->typeMapper) {
$keyType = $this->typeMapper->getClassByType($keyType);
}
- unset($data[self::KEY_TYPE]);
-
$data = $data[self::KEY_SCALAR] ?? $data;
- $data = $this->denormalize($data, $keyType, $format, $context);
- return parent::denormalize($data, $keyType, $format, $context);
- }
+ if (\is_array($data)) {
+ foreach ($data as $idx => $datum) {
+ $data[$idx] = $this->denormalize($datum, $keyType, $format, $context);
+ }
+ }
- if (is_iterable($data)) {
- $type = ('' === $type) ? 'stdClass' : $type;
+ return parent::denormalize($data, $keyType, $format, $context + [self::CONTEXT_SERIALIZER => $this]);
+ }
- return parent::denormalize($data, $type.'[]', $format, $context);
+ foreach ($data as $idx => $datum) {
+ $data[$idx] = $this->denormalize($datum, '', $format, $context);
}
return $data;
diff --git a/tests/Fixtures/Normalizer/VectorNormalizer.php b/tests/Fixtures/Normalizer/VectorNormalizer.php
new file mode 100644
index 0000000..355d600
--- /dev/null
+++ b/tests/Fixtures/Normalizer/VectorNormalizer.php
@@ -0,0 +1,52 @@
+ true,
+ ];
+ }
+
+ public function normalize(mixed $object, ?string $format = null, array $context = []): array
+ {
+ if (!$this->supportsNormalization($object)) {
+ throw new InvalidArgumentException(sprintf('The object must be an instance of "%s".', Vector::class));
+ }
+
+ return ['position' => $object->key(), '[]' => $object->getArray()];
+ }
+
+ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
+ {
+ return $data instanceof Vector;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): object
+ {
+ if (!$this->supportsDenormalization($data, $type)) {
+ throw NotNormalizableValueException::createForUnexpectedDataType('Data expected to be a array of shape {"position": int, "[]": array}.', $data, ['array'], $context['deserialization_path'] ?? null);
+ }
+
+ return new Vector($data['[]'], $data['position']);
+ }
+
+ public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
+ {
+ return is_array($data) && is_a($type, Vector::class, true);
+ }
+}
diff --git a/tests/Fixtures/TestBundle/DependencyInjection/InjectCustomNormalizerPass.php b/tests/Fixtures/TestBundle/DependencyInjection/InjectCustomNormalizerPass.php
index 77885d5..b7e0c4e 100644
--- a/tests/Fixtures/TestBundle/DependencyInjection/InjectCustomNormalizerPass.php
+++ b/tests/Fixtures/TestBundle/DependencyInjection/InjectCustomNormalizerPass.php
@@ -9,6 +9,7 @@
namespace Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\DependencyInjection;
+use Dunglas\DoctrineJsonOdm\Tests\Fixtures\Normalizer\VectorNormalizer;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -21,9 +22,16 @@ public function process(ContainerBuilder $container): void
{
$container->setDefinition('dunglas_doctrine_json_odm.normalizer.custom', new Definition(CustomNormalizer::class));
+ $vectorDefinition = new Definition(VectorNormalizer::class);
+ $vectorDefinition->addTag('serializer.normalizer');
+ $container->setDefinition(VectorNormalizer::class, $vectorDefinition);
+
$serializerDefinition = $container->getDefinition('dunglas_doctrine_json_odm.serializer');
$arguments = $serializerDefinition->getArguments();
- $arguments[0] = array_merge([new Reference('dunglas_doctrine_json_odm.normalizer.custom')], $arguments[0]);
+ $arguments[0] = array_merge([
+ new Reference(VectorNormalizer::class),
+ new Reference('dunglas_doctrine_json_odm.normalizer.custom'),
+ ], $arguments[0]);
$serializerDefinition->setArguments($arguments);
}
}
diff --git a/tests/Fixtures/TestBundle/Document/TraversableValue.php b/tests/Fixtures/TestBundle/Document/TraversableValue.php
new file mode 100644
index 0000000..1e2a87e
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/TraversableValue.php
@@ -0,0 +1,43 @@
+array = $array;
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return array_key_exists($offset, $this->array);
+ }
+
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->array[$offset];
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ $this->array[$offset] = $value;
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ unset($this->array[$offset]);
+ }
+
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->array);
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/Vector.php b/tests/Fixtures/TestBundle/Document/Vector.php
new file mode 100644
index 0000000..7e310d5
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/Vector.php
@@ -0,0 +1,48 @@
+array = $array;
+ $this->position = $position;
+ }
+
+ public function getArray(): array
+ {
+ return $this->array;
+ }
+
+ public function current(): mixed
+ {
+ return $this->array[$this->key()];
+ }
+
+ public function key(): mixed
+ {
+ return $this->position;
+ }
+
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ public function valid(): bool
+ {
+ return isset($this->array[$this->key()]);
+ }
+}
diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php
index 29d9792..5e628f9 100644
--- a/tests/SerializerTest.php
+++ b/tests/SerializerTest.php
@@ -16,6 +16,8 @@
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\Bar;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\Baz;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\ScalarValue;
+use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\TraversableValue;
+use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\Vector;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Document\WithMappedType;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Entity\Foo;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Enum\InputMode;
@@ -241,4 +243,39 @@ public function testSerializeUid(): void
$this->assertEquals($value, $restoredValue);
}
+
+ /** Uses {@link VectorNormalizer} to normalize Vector, otherwise it will be treated as Traversable and fail */
+ public function testSerializeObjectWithConfiguredNormalizer(): void
+ {
+ $serializer = self::$kernel->getContainer()->get('dunglas_doctrine_json_odm.serializer');
+
+ $attribute = new Attribute();
+ $attribute->key = 'foo';
+ $attribute->value = 'bar';
+
+ $vector = new Vector([$attribute, 2, 3, 4, 5]);
+ $vector->next();
+
+ $data = $serializer->serialize($vector, 'json');
+ $restoredVector = $serializer->deserialize($data, '', 'json');
+
+ $this->assertEquals($vector, $restoredVector);
+ }
+
+ /** {@see \Dunglas\DoctrineJsonOdm\Normalizer\TraversableNormalizer} */
+ public function testTraversableNormalizer(): void
+ {
+ $serializer = self::$kernel->getContainer()->get('dunglas_doctrine_json_odm.serializer');
+
+ $attribute = new Attribute();
+ $attribute->key = 'foo';
+ $attribute->value = 'bar';
+
+ $vector = new TraversableValue([$attribute, 'x' => 2, 'y'=>[3], 'z'=>'4', 5, ['' => null]]);
+
+ $data = $serializer->serialize($vector, 'json');
+ $restoredVector = $serializer->deserialize($data, '', 'json');
+
+ $this->assertEquals($vector, $restoredVector);
+ }
}