Skip to content

Commit

Permalink
Merge pull request #3 from KurtThiemann/custom-property-serializer
Browse files Browse the repository at this point in the history
Add custom serializers and deserializers for array items
  • Loading branch information
JulianVennen authored Oct 28, 2024
2 parents 6e05bfb + 7c78a86 commit 87b7426
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 26 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ protected TestClass $example;
Note that the custom Deserializer is responsible for returning the correct type.
If an incompatible type is returned, an IncorrectTypeException is thrown.

#### Item Serializer and Item Deserializer

Custom Serializers and Deserializers can also be specified for array items.

```php
#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(TestClass::class))]
protected array $example = [];
```

### Exceptions
The following exceptions may be thrown during serialization or deserialization:
- [MissingPropertyException](#missingpropertyexception)
Expand Down
4 changes: 2 additions & 2 deletions src/ArrayDeserializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ protected function deserializeProperty(
);
}

if (is_array($value) && $attribute->getItemType() !== null) {
$deserializer = new static($attribute->getItemType());
if (is_array($value) && ($attribute->getItemType() !== null || $attribute->getItemDeserializer() !== null)) {
$deserializer = $attribute->getItemDeserializer() ?? new static($attribute->getItemType());
$value = array_map(fn($item) => $deserializer->deserialize($item, $path . "." . $name), $value);
}

Expand Down
9 changes: 8 additions & 1 deletion src/ArraySerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,15 @@ public function serialize(object $item): array
$value = $customSerializer->serialize($value);
} else if ($value instanceof JsonSerializable) {
$value = $value->jsonSerialize();
} elseif (is_object($value)) {
} else if (is_object($value)) {
$value = $this->serialize($value);
} else if (is_array($value) && $customSerializer = $attribute->getItemSerializer()) {
foreach ($value as $key => $item) {
if (!is_object($item)) {
throw new IncorrectTypeException($property->getName(), "object", $item);
}
$value[$key] = $customSerializer->serialize($item);
}
}

$serializedProperties[$name] = $value;
Expand Down
18 changes: 18 additions & 0 deletions src/Serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public function __construct(
protected ?string $itemType = null,
protected ?SerializerInterface $serializer = null,
protected ?DeserializerInterface $deserializer = null,
protected ?SerializerInterface $itemSerializer = null,
protected ?DeserializerInterface $itemDeserializer = null,
)
{
}
Expand Down Expand Up @@ -78,4 +80,20 @@ public function getDeserializer(): ?DeserializerInterface
{
return $this->deserializer;
}

/**
* @return SerializerInterface|null
*/
public function getItemSerializer(): ?SerializerInterface
{
return $this->itemSerializer;
}

/**
* @return DeserializerInterface|null
*/
public function getItemDeserializer(): ?DeserializerInterface
{
return $this->itemDeserializer;
}
}
11 changes: 3 additions & 8 deletions tests/src/CustomSerializerInvalidTypeTestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@ class CustomSerializerInvalidTypeTestClass implements JsonSerializable
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(SecondTestClass::class))]
protected TestClass $testClass;

#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(SecondTestClass::class))]
protected array $testArray = [];

public function __construct()
{
$this->testClass = new TestClass();
}

/**
* @return TestClass
*/
public function getTestClass(): TestClass
{
return $this->testClass;
}
}
31 changes: 26 additions & 5 deletions tests/src/CustomSerializerTestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,40 @@ class CustomSerializerTestClass implements JsonSerializable
{
use PropertyJsonSerializer;

#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class))]
protected TestClass $testClass;
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(SecondTestClass::class))]
protected SecondTestClass $testClass;

#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(SecondTestClass::class))]
protected array $testArray = [];

public function __construct()
{
$this->testClass = new TestClass();
$this->testClass = new SecondTestClass();
$this->testArray = [new SecondTestClass(), new SecondTestClass()];
}

/**
* @return TestClass
* @return SecondTestClass
*/
public function getTestClass(): TestClass
public function getTestClass(): SecondTestClass
{
return $this->testClass;
}

/**
* @return SecondTestClass[]
*/
public function getTestArray(): array
{
return $this->testArray;
}

/**
* @param array $testArray
* @return void
*/
public function setTestArray(array $testArray): void
{
$this->testArray = $testArray;
}
}
12 changes: 8 additions & 4 deletions tests/tests/DeserializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Aternos\Serializer\Test\Src\CustomSerializerTestClass;
use Aternos\Serializer\Test\Src\DefaultValueTestClass;
use Aternos\Serializer\Test\Src\IntersectionTestClass;
use Aternos\Serializer\Test\Src\SecondTestClass;
use Aternos\Serializer\Test\Src\SerializerTestClass;
use Aternos\Serializer\Test\Src\TestClass;
use Aternos\Serializer\Test\Src\UnionIntersectionTestClass;
Expand Down Expand Up @@ -496,16 +497,19 @@ public function testArrayDeserializerArgumentIsNotAnArray(): void
public function testCustomDeserializer(): void
{
$deserializer = new JsonDeserializer(CustomSerializerTestClass::class);
$testClass = $deserializer->deserialize('{"testClass":"TzozNzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFRlc3RDbGFzcyI6OTp7czo2OiIAKgBhZ2UiO2k6MDtzOjE1OiIAKgBvcmlnaW5hbE5hbWUiO047czoxMToiACoAbnVsbGFibGUiO047czoxMjoiACoAYm9vbE9ySW50IjtiOjA7czoxNjoiACoAbm90QUpzb25GaWVsZCI7czo0OiJ0ZXN0IjtzOjE4OiIAKgBzZWNvbmRUZXN0Q2xhc3MiO047czo4OiIAKgBtaXhlZCI7TjtzOjg6IgAqAGZsb2F0IjtOO3M6ODoiACoAYXJyYXkiO047fQ=="}');
$testClass = $deserializer->deserialize('{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","testArray":["Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="]}');
$this->assertInstanceOf(CustomSerializerTestClass::class, $testClass);
$this->assertInstanceOf(TestClass::class, $testClass->getTestClass());
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestClass());
$this->assertIsArray($testClass->getTestArray());
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestArray()[0]);
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestArray()[1]);
}

public function testCustomDeserializerReturnsInvalidType(): void
{
$deserializer = new JsonDeserializer(CustomSerializerInvalidTypeTestClass::class);
$this->expectException(IncorrectTypeException::class);
$this->expectExceptionMessage("Expected '.testClass' to be 'Aternos\Serializer\Test\Src\TestClass' found: \Aternos\Serializer\Test\Src\SecondTestClass::__set_state(array(\n))");
$deserializer->deserialize('{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="}');
$this->expectExceptionMessage("Expected '.testClass' to be 'Aternos\Serializer\Test\Src\TestClass' found: \Aternos\Serializer\Test\Src\BuiltInTypeTestClass::");
$deserializer->deserialize('{"testClass":"Tzo0ODoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXEJ1aWx0SW5UeXBlVGVzdENsYXNzIjo4OntzOjM6ImludCI7TjtzOjU6ImZsb2F0IjtOO3M6Njoic3RyaW5nIjtOO3M6NToiYXJyYXkiO047czo2OiJvYmplY3QiO047czo0OiJzZWxmIjtOO3M6NToiZmFsc2UiO047czo0OiJ0cnVlIjtOO30="}');
}
}
13 changes: 8 additions & 5 deletions tests/tests/SerializeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ class SerializeTest extends TestCase

protected string $nonSerializedName = "this isn't serialized";

#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class))]
protected TestClass $testClass;

public function testConstruct(): void
{
$property = new Serialize(
Expand Down Expand Up @@ -81,10 +78,16 @@ public function testGetItemType(): void

public function testGetCustomSerializerAndDeserializer(): void
{
$property = new ReflectionProperty($this, "testClass");
$attribute = Serialize::getAttribute($property);
$attribute = new Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class));
$this->assertNotNull($attribute);
$this->assertInstanceOf(Base64Serializer::class, $attribute->getSerializer());
$this->assertInstanceOf(Base64Deserializer::class, $attribute->getDeserializer());
}

public function testGetCustomItemSerializerAndDeserializer(): void
{
$attribute = new Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(TestClass::class));
$this->assertInstanceOf(Base64Serializer::class, $attribute->getItemSerializer());
$this->assertInstanceOf(Base64Deserializer::class, $attribute->getItemDeserializer());
}
}
10 changes: 9 additions & 1 deletion tests/tests/SerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,15 @@ public function testSerializeBuiltInTypes(): void
public function testCustomSerializer(): void
{
$testClass = new CustomSerializerTestClass();
$expected = '{"testClass":"TzozNzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFRlc3RDbGFzcyI6OTp7czo2OiIAKgBhZ2UiO2k6MDtzOjE1OiIAKgBvcmlnaW5hbE5hbWUiO047czoxMToiACoAbnVsbGFibGUiO047czoxMjoiACoAYm9vbE9ySW50IjtiOjA7czoxNjoiACoAbm90QUpzb25GaWVsZCI7czo0OiJ0ZXN0IjtzOjE4OiIAKgBzZWNvbmRUZXN0Q2xhc3MiO047czo4OiIAKgBtaXhlZCI7TjtzOjg6IgAqAGZsb2F0IjtOO3M6ODoiACoAYXJyYXkiO047fQ=="}';
$expected = '{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","testArray":["Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="]}';
$this->assertEquals($expected, json_encode($testClass));
}

public function testCustomItemSerializerThrowsIfItemIsNotAnObject(): void
{
$testClass = new CustomSerializerTestClass();
$testClass->setTestArray([1]);
$this->expectException(IncorrectTypeException::class);
json_encode($testClass);
}
}

0 comments on commit 87b7426

Please sign in to comment.