Skip to content

Commit

Permalink
Allow weak mode to apply to values in an array, not just the array it…
Browse files Browse the repository at this point in the history
…self.
  • Loading branch information
Crell committed Sep 6, 2024
1 parent 055e12a commit e79efd5
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ This key only applies on deserialization. If set to `true`, a type mismatch in

For sequence fields, `strict` set to `true` will reject a non-sequence value. (It must pass an `array_is_list()` check.) If `strict` is `false`, any array-ish value will be accepted but passed through `array_values()` to discard any keys and reindex it.

Additionally, in non-`strict` mode, numeric strings in the incoming array will be cast to ints or floats as appropriate in both sequence fields and dictionary fields. In `strict` mode, numeric strings will still be rejected.

The exact handling of this setting may vary slightly depending on the incoming format, as some formats handle their own types differently. (For instance, everything is a string in XML.)

### `requireValue` (bool, default false)
Expand Down
6 changes: 6 additions & 0 deletions src/Formatter/ArrayBasedDeformatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ public function deserializeSequence(mixed $decoded, Field $field, Deserializer $
// @phpstan-ignore-next-line
$class = $field?->typeField?->arrayType ?? '';
if ($class instanceof ValueType) {
if (!$field->strict) {
$data = $class->coerce($data);
}
if ($class->assert($data)) {
return $data;
} else {
Expand Down Expand Up @@ -179,6 +182,9 @@ public function deserializeDictionary(mixed $decoded, Field $field, Deserializer
// @phpstan-ignore-next-line
$class = $field?->typeField?->arrayType ?? '';
if ($class instanceof ValueType) {
if (!$field->strict) {
$data = $class->coerce($data);
}
if ($class->assert($data)) {
return $data;
} else {
Expand Down
11 changes: 11 additions & 0 deletions src/ValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Crell\Serde;

use function Crell\fp\all;
use function Crell\fp\amap;

enum ValueType
{
Expand All @@ -24,4 +25,14 @@ public function assert(array $values): bool
self::Array => all(is_array(...))($values),
};
}

public function coerce(array $values): array

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.1

Method Crell\Serde\ValueType::coerce() has parameter $values with no value type specified in iterable type array.

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.1

Method Crell\Serde\ValueType::coerce() return type has no value type specified in iterable type array.

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.2

Method Crell\Serde\ValueType::coerce() has parameter $values with no value type specified in iterable type array.

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.2

Method Crell\Serde\ValueType::coerce() return type has no value type specified in iterable type array.

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.3

Method Crell\Serde\ValueType::coerce() has parameter $values with no value type specified in iterable type array.

Check failure on line 29 in src/ValueType.php

View workflow job for this annotation

GitHub Actions / PHPStan checks on 8.3

Method Crell\Serde\ValueType::coerce() return type has no value type specified in iterable type array.
{
return match ($this) {
self::String => $values,
self::Int => amap(intval(...))($values),
self::Float => amap(floatval(...))($values),
self::Array => $values,
};
}
}
5 changes: 5 additions & 0 deletions tests/ArrayFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public function setUp(): void
'strict' => ['A', 'B'],
'nonstrict' => ['a' => 'A', 'b' => 'B'],
];

$this->weakModeLists = [
'seq' => [1, '2', 3],
'dict' => ['a' => 1, 'b' => '2'],
];
}

protected function arrayify(mixed $serialized): array
Expand Down
5 changes: 5 additions & 0 deletions tests/JsonFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public function setUp(): void
'strict' => ['A', 'B'],
'nonstrict' => ['a' => 'A', 'b' => 'B'],
]);

$this->weakModeLists = json_encode([
'seq' => [1, '2', 3],
'dict' => ['a' => 1, 'b' => '2'],
]);
}

protected function arrayify(mixed $serialized): array
Expand Down
21 changes: 21 additions & 0 deletions tests/Records/WeakLists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Records;

use Crell\Serde\Attributes\DictionaryField;
use Crell\Serde\Attributes\Field;
use Crell\Serde\Attributes\SequenceField;
use Crell\Serde\KeyType;
use Crell\Serde\ValueType;

class WeakLists
{
public function __construct(
#[Field(strict: false), SequenceField(ValueType::Int)]
public array $seq,
#[Field(strict: false), DictionaryField(arrayType: ValueType::Int, keyType: KeyType::String)]
public array $dict,
) {}
}
26 changes: 25 additions & 1 deletion tests/SerdeTestCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
use Crell\Serde\Records\ValueObjects\JobEntryFlattenedPrefixed;
use Crell\Serde\Records\ValueObjects\Person;
use Crell\Serde\Records\Visibility;
use Crell\Serde\Records\WeakLists;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
Expand Down Expand Up @@ -159,6 +160,13 @@ abstract class SerdeTestCases extends TestCase
*/
protected mixed $dictsInSequenceShouldFail;

/**
* Data to deserialize that contains numeric-string lists, which should still coerce into an integer list safely.
*
* @see lists_in_weak_mode_coerce_elements())
*/
protected mixed $weakModeLists;

/**
* Data to deserialize that should pass, because the strict is valid and non-strict gets coerced to a list.
* @see non_sequence_arrays_in_weak_mode_are_coerced
Expand Down Expand Up @@ -1281,7 +1289,7 @@ public function nullable_null_properties_are_allowed(): void
self::assertEquals($data, $result);
}

#[Test]
#[Test, Group('flattening')]
public function nullable_properties_flattened(): void
{
$s = new SerdeCommon(formatters: $this->formatters);
Expand All @@ -1298,6 +1306,22 @@ public function nullable_properties_flattened(): void
self::assertEquals($data, $result);
}

#[Test]
public function lists_in_weak_mode_coerce_elements(): void
{
$s = new SerdeCommon(formatters: $this->formatters);

/** @var WeakLists $result */
$result = $s->deserialize($this->weakModeLists, $this->format, WeakLists::class);

self::assertIsArray($result->seq);
self::assertIsArray($result->dict);

foreach ([...$result->seq, ...$result->dict] as $val) {
self::assertIsInt($val);
}
}

#[Test]
public function non_sequence_arrays_are_normalized_to_sequences(): void
{
Expand Down
5 changes: 5 additions & 0 deletions tests/YamlFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public function setUp(): void
'strict' => ['A', 'B'],
'nonstrict' => ['a' => 'A', 'b' => 'B'],
]);

$this->weakModeLists = YAML::dump([
'seq' => [1, '2', 3],
'dict' => ['a' => 1, 'b' => '2'],
]);
}

protected function arrayify(mixed $serialized): array
Expand Down

0 comments on commit e79efd5

Please sign in to comment.