Skip to content

Commit a42ba4b

Browse files
committed
Allow specifying a preferred object type to cast incoming array data to, if the data is array-ish.
1 parent b756cd9 commit a42ba4b

File tree

4 files changed

+75
-17
lines changed

4 files changed

+75
-17
lines changed

src/Attributes/MixedField.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Attributes;
6+
7+
use Crell\AttributeUtils\SupportsScopes;
8+
use Crell\Serde\TypeField;
9+
use Attribute;
10+
11+
#[Attribute(Attribute::TARGET_PROPERTY)]
12+
class MixedField implements TypeField, SupportsScopes
13+
{
14+
public function __construct(
15+
public readonly string $suggestedType,
16+
protected readonly array $scopes = [null],
17+
) {}
18+
19+
public function scopes(): array
20+
{
21+
return $this->scopes;
22+
}
23+
24+
public function acceptsType(string $type): bool
25+
{
26+
return $type === 'mixed';
27+
}
28+
29+
public function validate(mixed $value): bool
30+
{
31+
return true;
32+
}
33+
}

src/PropertyHandler/MixedExporter.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Crell\Serde\Attributes\DictionaryField;
88
use Crell\Serde\Attributes\Field;
9+
use Crell\Serde\Attributes\MixedField;
910
use Crell\Serde\CollectionItem;
1011
use Crell\Serde\Deserializer;
1112
use Crell\Serde\Dict;
@@ -41,6 +42,12 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
4142
// it directly.
4243
$type = \get_debug_type($source[$field->serializedName]);
4344

45+
/** @var MixedField|null $typeField */
46+
$typeField = $field->typeField;
47+
if ($typeField && class_exists($typeField->suggestedType) && $type === 'array') {
48+
$type = $typeField->suggestedType;
49+
}
50+
4451
return $deserializer->deserialize($source, Field::create(
4552
serializedName: $field->serializedName,
4653
phpType: $type,

tests/Records/MixedValObject.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Records;
6+
7+
use Crell\Serde\Attributes\MixedField;
8+
9+
class MixedValObject
10+
{
11+
public function __construct(
12+
#[MixedField(Point::class)]
13+
public mixed $val
14+
) {}
15+
}

tests/SerdeTestCases.php

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use Crell\Serde\Records\MappedCollected\ThingC;
5050
use Crell\Serde\Records\MappedCollected\ThingList;
5151
use Crell\Serde\Records\MixedVal;
52+
use Crell\Serde\Records\MixedValObject;
5253
use Crell\Serde\Records\MultiCollect\ThingOneA;
5354
use Crell\Serde\Records\MultiCollect\ThingTwoC;
5455
use Crell\Serde\Records\MultiCollect\Wrapper;
@@ -1060,30 +1061,32 @@ public static function mixed_val_property_examples(): iterable
10601061
yield 'float' => [new MixedVal(3.14)];
10611062
yield 'sequence' => [new MixedVal(['a', 'b', 'c'])];
10621063
yield 'dict' => [new MixedVal(['a' => 'A', 'b' => 'B', 'c' => 'C'])];
1063-
yield 'object' => [new MixedVal(new Point(1, 2, 3))];
10641064
}
10651065

1066-
public function mixed_val_property_validate(mixed $serialized, mixed $data): void
1066+
#[Test, DataProvider('mixed_val_property_object_examples')]
1067+
public function mixed_val_property_object(mixed $data): void
10671068
{
1068-
}
1069+
$s = new SerdeCommon(formatters: $this->formatters);
10691070

1070-
/**
1071-
* This isn't a desired feature; it's just confirmation for the future why it is how it is.
1072-
*/
1073-
#[Test]
1074-
public function mixed_val_object_does_not_serialize(): void
1075-
{
1076-
// MixedExporter sends the property value back through the Serialize pipeline
1077-
// a second time with a new Field definition. However, that trips the circular
1078-
// reference detection. Ideally we will fix that somehow, but I'm not sure how.
1079-
// Importing an object to mixed will never work correctly.
1080-
$this->expectException(CircularReferenceDetected::class);
1071+
$serialized = $s->serialize($data, $this->format);
1072+
1073+
$this->mixed_val_property_validate($serialized, $data);
10811074

1082-
$data = new MixedVal(new Point(3, 4, 5));
1075+
$result = $s->deserialize($serialized, from: $this->format, to: MixedValObject::class);
10831076

1084-
$s = new SerdeCommon(formatters: $this->formatters);
1077+
self::assertEquals($data, $result);
1078+
}
10851079

1086-
$serialized = $s->serialize($data, $this->format);
1080+
public static function mixed_val_property_object_examples(): iterable
1081+
{
1082+
yield 'string' => [new MixedValObject('hello')];
1083+
yield 'int' => [new MixedValObject(5)];
1084+
yield 'float' => [new MixedValObject(3.14)];
1085+
yield 'object' => [new MixedValObject(new Point(1, 2, 3))];
1086+
}
1087+
1088+
public function mixed_val_property_validate(mixed $serialized, mixed $data): void
1089+
{
10871090
}
10881091

10891092
#[Test]

0 commit comments

Comments
 (0)