Skip to content

Commit

Permalink
Simplify MixedExporter to just defer back to the serializer/deseriali…
Browse files Browse the repository at this point in the history
…zer, so it runs through the correct exporter. MixedExporter just does type detection only.
  • Loading branch information
Crell committed Oct 16, 2023
1 parent d29297c commit 76a7656
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 63 deletions.
79 changes: 17 additions & 62 deletions src/PropertyHandler/MixedExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,81 +15,36 @@
use Crell\Serde\Serializer;
use Crell\Serde\TypeCategory;

/**
* Exporter/importer for `mixed` properties.
*
* This class makes a good-faith attempt to detect the type of a given field by its value.
* It currently does not work for objects, and on import it works only on array-based
* formats (JSON, YAML, etc.)
*/
class MixedExporter implements Importer, Exporter
{
public function exportValue(Serializer $serializer, Field $field, mixed $value, mixed $runningValue): mixed
{
$type = \get_debug_type($value);

if ($type === 'array') {
if (array_is_list($value)) {
return $serializer->formatter->serializeSequence($runningValue, $field, $this->arrayToSequence($value), $serializer);
} else {
$dict = $this->arrayToDict($value, $field);
return $serializer->formatter->serializeDictionary($runningValue, $field, $dict, $serializer);
}
}

return match ($type) {
'int' => $serializer->formatter->serializeInt($runningValue, $field, $value),
'float' => $serializer->formatter->serializeFloat($runningValue, $field, $value),
'bool' => $serializer->formatter->serializeBool($runningValue, $field, $value),
'string' => $serializer->formatter->serializeString($runningValue, $field, $value),
};
}

/**
* @param array<mixed> $value
*/
protected function arrayToSequence(array $value): Sequence
{
$items = [];
foreach ($value as $k => $v) {
$f = Field::create(serializedName: "$k", phpType: \get_debug_type($v));
$items[] = new CollectionItem(field: $f, value: $v);
}
return new Sequence($items);
}

/**
* @param array<mixed, mixed> $value
*/
protected function arrayToDict(array $value, Field $field): Dict
{
/** @var DictionaryField|null $typeField */
$typeField = $field->typeField;

$items = [];
foreach ($value as $k => $v) {
// Most $runningValue implementations will be an array.
// Arrays in PHP force-cast an integer-string key to
// an integer. That means we cannot guarantee the type
// of the key going out in the Exporter. The Formatter
// will have to do so, if it cares. However, we can still
// detect and reject string-in-int.
if ($typeField?->keyType === KeyType::Int && \get_debug_type($k) === 'string') {
// It's an int field, but the key is a string. That's a no-no.
throw InvalidArrayKeyType::create($field, 'string');
}
$f = Field::create(serializedName: "$k", phpType: \get_debug_type($v));
$items[] = new CollectionItem(field: $f, value: $v);
}

return new Dict($items);
return $serializer->serialize($value, $runningValue, Field::create(
serializedName: $field->serializedName,
phpType: $type,
));
}

public function importValue(Deserializer $deserializer, Field $field, mixed $source): mixed
{
// This is normally a bad idea, as the $source should be opaque. In this
// case, we're guaranteed that the $source is array-based, so we can introspect
// it directly.
return match (\get_debug_type($source[$field->serializedName])) {
'int' => $deserializer->deformatter->deserializeInt($source, $field),
'float' => $deserializer->deformatter->deserializeFloat($source, $field),
'bool' => $deserializer->deformatter->deserializeBool($source, $field),
'string' => $deserializer->deformatter->deserializeString($source, $field),
'array' => $deserializer->deformatter->deserializeDictionary($source, $field, $deserializer),
};
$type = \get_debug_type($source[$field->serializedName]);

return $deserializer->deserialize($source, Field::create(
serializedName: $field->serializedName,
phpType: $type,
));
}

public function canExport(Field $field, mixed $value, string $format): bool
Expand Down
3 changes: 2 additions & 1 deletion tests/SerdeTestCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,8 @@ public static function mixed_val_property_examples(): iterable
yield 'string' => [new MixedVal('hello')];
yield 'int' => [new MixedVal(5)];
yield 'float' => [new MixedVal(3.14)];
yield 'array' => [new MixedVal(['a', 'b', 'c'])];
yield 'sequence' => [new MixedVal(['a', 'b', 'c'])];
yield 'dict' => [new MixedVal(['a' => 'A', 'b' => 'B', 'c' => 'C'])];
// Objects can't work, because they cannot be imported without type data.
// Exporting might. Todo for later.
//yield 'object' => [new Point(3, 4, 5)];
Expand Down

0 comments on commit 76a7656

Please sign in to comment.