Skip to content

Commit 87b7426

Browse files
authored
Merge pull request #3 from KurtThiemann/custom-property-serializer
Add custom serializers and deserializers for array items
2 parents 6e05bfb + 7c78a86 commit 87b7426

9 files changed

+91
-26
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ protected TestClass $example;
188188
Note that the custom Deserializer is responsible for returning the correct type.
189189
If an incompatible type is returned, an IncorrectTypeException is thrown.
190190

191+
#### Item Serializer and Item Deserializer
192+
193+
Custom Serializers and Deserializers can also be specified for array items.
194+
195+
```php
196+
#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(TestClass::class))]
197+
protected array $example = [];
198+
```
199+
191200
### Exceptions
192201
The following exceptions may be thrown during serialization or deserialization:
193202
- [MissingPropertyException](#missingpropertyexception)

src/ArrayDeserializer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ protected function deserializeProperty(
154154
);
155155
}
156156

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

src/ArraySerializer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,15 @@ public function serialize(object $item): array
7171
$value = $customSerializer->serialize($value);
7272
} else if ($value instanceof JsonSerializable) {
7373
$value = $value->jsonSerialize();
74-
} elseif (is_object($value)) {
74+
} else if (is_object($value)) {
7575
$value = $this->serialize($value);
76+
} else if (is_array($value) && $customSerializer = $attribute->getItemSerializer()) {
77+
foreach ($value as $key => $item) {
78+
if (!is_object($item)) {
79+
throw new IncorrectTypeException($property->getName(), "object", $item);
80+
}
81+
$value[$key] = $customSerializer->serialize($item);
82+
}
7683
}
7784

7885
$serializedProperties[$name] = $value;

src/Serialize.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public function __construct(
3636
protected ?string $itemType = null,
3737
protected ?SerializerInterface $serializer = null,
3838
protected ?DeserializerInterface $deserializer = null,
39+
protected ?SerializerInterface $itemSerializer = null,
40+
protected ?DeserializerInterface $itemDeserializer = null,
3941
)
4042
{
4143
}
@@ -78,4 +80,20 @@ public function getDeserializer(): ?DeserializerInterface
7880
{
7981
return $this->deserializer;
8082
}
83+
84+
/**
85+
* @return SerializerInterface|null
86+
*/
87+
public function getItemSerializer(): ?SerializerInterface
88+
{
89+
return $this->itemSerializer;
90+
}
91+
92+
/**
93+
* @return DeserializerInterface|null
94+
*/
95+
public function getItemDeserializer(): ?DeserializerInterface
96+
{
97+
return $this->itemDeserializer;
98+
}
8199
}

tests/src/CustomSerializerInvalidTypeTestClass.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@ class CustomSerializerInvalidTypeTestClass implements JsonSerializable
1313
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(SecondTestClass::class))]
1414
protected TestClass $testClass;
1515

16+
#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(SecondTestClass::class))]
17+
protected array $testArray = [];
18+
1619
public function __construct()
1720
{
1821
$this->testClass = new TestClass();
1922
}
20-
21-
/**
22-
* @return TestClass
23-
*/
24-
public function getTestClass(): TestClass
25-
{
26-
return $this->testClass;
27-
}
2823
}

tests/src/CustomSerializerTestClass.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,40 @@ class CustomSerializerTestClass implements JsonSerializable
1010
{
1111
use PropertyJsonSerializer;
1212

13-
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class))]
14-
protected TestClass $testClass;
13+
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(SecondTestClass::class))]
14+
protected SecondTestClass $testClass;
15+
16+
#[Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(SecondTestClass::class))]
17+
protected array $testArray = [];
1518

1619
public function __construct()
1720
{
18-
$this->testClass = new TestClass();
21+
$this->testClass = new SecondTestClass();
22+
$this->testArray = [new SecondTestClass(), new SecondTestClass()];
1923
}
2024

2125
/**
22-
* @return TestClass
26+
* @return SecondTestClass
2327
*/
24-
public function getTestClass(): TestClass
28+
public function getTestClass(): SecondTestClass
2529
{
2630
return $this->testClass;
2731
}
32+
33+
/**
34+
* @return SecondTestClass[]
35+
*/
36+
public function getTestArray(): array
37+
{
38+
return $this->testArray;
39+
}
40+
41+
/**
42+
* @param array $testArray
43+
* @return void
44+
*/
45+
public function setTestArray(array $testArray): void
46+
{
47+
$this->testArray = $testArray;
48+
}
2849
}

tests/tests/DeserializerTest.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Aternos\Serializer\Test\Src\CustomSerializerTestClass;
1616
use Aternos\Serializer\Test\Src\DefaultValueTestClass;
1717
use Aternos\Serializer\Test\Src\IntersectionTestClass;
18+
use Aternos\Serializer\Test\Src\SecondTestClass;
1819
use Aternos\Serializer\Test\Src\SerializerTestClass;
1920
use Aternos\Serializer\Test\Src\TestClass;
2021
use Aternos\Serializer\Test\Src\UnionIntersectionTestClass;
@@ -496,16 +497,19 @@ public function testArrayDeserializerArgumentIsNotAnArray(): void
496497
public function testCustomDeserializer(): void
497498
{
498499
$deserializer = new JsonDeserializer(CustomSerializerTestClass::class);
499-
$testClass = $deserializer->deserialize('{"testClass":"TzozNzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFRlc3RDbGFzcyI6OTp7czo2OiIAKgBhZ2UiO2k6MDtzOjE1OiIAKgBvcmlnaW5hbE5hbWUiO047czoxMToiACoAbnVsbGFibGUiO047czoxMjoiACoAYm9vbE9ySW50IjtiOjA7czoxNjoiACoAbm90QUpzb25GaWVsZCI7czo0OiJ0ZXN0IjtzOjE4OiIAKgBzZWNvbmRUZXN0Q2xhc3MiO047czo4OiIAKgBtaXhlZCI7TjtzOjg6IgAqAGZsb2F0IjtOO3M6ODoiACoAYXJyYXkiO047fQ=="}');
500+
$testClass = $deserializer->deserialize('{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","testArray":["Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="]}');
500501
$this->assertInstanceOf(CustomSerializerTestClass::class, $testClass);
501-
$this->assertInstanceOf(TestClass::class, $testClass->getTestClass());
502+
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestClass());
503+
$this->assertIsArray($testClass->getTestArray());
504+
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestArray()[0]);
505+
$this->assertInstanceOf(SecondTestClass::class, $testClass->getTestArray()[1]);
502506
}
503507

504508
public function testCustomDeserializerReturnsInvalidType(): void
505509
{
506510
$deserializer = new JsonDeserializer(CustomSerializerInvalidTypeTestClass::class);
507511
$this->expectException(IncorrectTypeException::class);
508-
$this->expectExceptionMessage("Expected '.testClass' to be 'Aternos\Serializer\Test\Src\TestClass' found: \Aternos\Serializer\Test\Src\SecondTestClass::__set_state(array(\n))");
509-
$deserializer->deserialize('{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="}');
512+
$this->expectExceptionMessage("Expected '.testClass' to be 'Aternos\Serializer\Test\Src\TestClass' found: \Aternos\Serializer\Test\Src\BuiltInTypeTestClass::");
513+
$deserializer->deserialize('{"testClass":"Tzo0ODoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXEJ1aWx0SW5UeXBlVGVzdENsYXNzIjo4OntzOjM6ImludCI7TjtzOjU6ImZsb2F0IjtOO3M6Njoic3RyaW5nIjtOO3M6NToiYXJyYXkiO047czo2OiJvYmplY3QiO047czo0OiJzZWxmIjtOO3M6NToiZmFsc2UiO047czo0OiJ0cnVlIjtOO30="}');
510514
}
511515
}

tests/tests/SerializeTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ class SerializeTest extends TestCase
2121

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

24-
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class))]
25-
protected TestClass $testClass;
26-
2724
public function testConstruct(): void
2825
{
2926
$property = new Serialize(
@@ -81,10 +78,16 @@ public function testGetItemType(): void
8178

8279
public function testGetCustomSerializerAndDeserializer(): void
8380
{
84-
$property = new ReflectionProperty($this, "testClass");
85-
$attribute = Serialize::getAttribute($property);
81+
$attribute = new Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class));
8682
$this->assertNotNull($attribute);
8783
$this->assertInstanceOf(Base64Serializer::class, $attribute->getSerializer());
8884
$this->assertInstanceOf(Base64Deserializer::class, $attribute->getDeserializer());
8985
}
86+
87+
public function testGetCustomItemSerializerAndDeserializer(): void
88+
{
89+
$attribute = new Serialize(itemSerializer: new Base64Serializer(), itemDeserializer: new Base64Deserializer(TestClass::class));
90+
$this->assertInstanceOf(Base64Serializer::class, $attribute->getItemSerializer());
91+
$this->assertInstanceOf(Base64Deserializer::class, $attribute->getItemDeserializer());
92+
}
9093
}

tests/tests/SerializerTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,15 @@ public function testSerializeBuiltInTypes(): void
128128
public function testCustomSerializer(): void
129129
{
130130
$testClass = new CustomSerializerTestClass();
131-
$expected = '{"testClass":"TzozNzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFRlc3RDbGFzcyI6OTp7czo2OiIAKgBhZ2UiO2k6MDtzOjE1OiIAKgBvcmlnaW5hbE5hbWUiO047czoxMToiACoAbnVsbGFibGUiO047czoxMjoiACoAYm9vbE9ySW50IjtiOjA7czoxNjoiACoAbm90QUpzb25GaWVsZCI7czo0OiJ0ZXN0IjtzOjE4OiIAKgBzZWNvbmRUZXN0Q2xhc3MiO047czo4OiIAKgBtaXhlZCI7TjtzOjg6IgAqAGZsb2F0IjtOO3M6ODoiACoAYXJyYXkiO047fQ=="}';
131+
$expected = '{"testClass":"Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","testArray":["Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ==","Tzo0MzoiQXRlcm5vc1xTZXJpYWxpemVyXFRlc3RcU3JjXFNlY29uZFRlc3RDbGFzcyI6MDp7fQ=="]}';
132132
$this->assertEquals($expected, json_encode($testClass));
133133
}
134+
135+
public function testCustomItemSerializerThrowsIfItemIsNotAnObject(): void
136+
{
137+
$testClass = new CustomSerializerTestClass();
138+
$testClass->setTestArray([1]);
139+
$this->expectException(IncorrectTypeException::class);
140+
json_encode($testClass);
141+
}
134142
}

0 commit comments

Comments
 (0)