Skip to content

Commit

Permalink
Merge pull request #54 from waahhhh/nullable-values
Browse files Browse the repository at this point in the history
Nullable values
  • Loading branch information
Crell committed May 8, 2024
2 parents 44e3af8 + e452d98 commit f3c429b
Show file tree
Hide file tree
Showing 10 changed files with 42 additions and 32 deletions.
44 changes: 26 additions & 18 deletions src/Formatter/ArrayBasedDeformatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@
*/
trait ArrayBasedDeformatter
{
public function deserializeInt(mixed $decoded, Field $field): int|DeformatterResult
public function deserializeInt(mixed $decoded, Field $field): int|DeformatterResult|null
{
if (!isset($decoded[$field->serializedName])) {
if (!array_key_exists($field->serializedName, $decoded)) {
return DeformatterResult::Missing;
}

$value = $decoded[$field->serializedName];

if ($field->strict) {
if (!is_int($decoded[$field->serializedName])) {
if (!is_int($value) && !($field->nullable && is_null($value))) {
throw TypeMismatch::create($field->serializedName, 'int', \get_debug_type($decoded[$field->serializedName]));
}
return $decoded[$field->serializedName];
Expand All @@ -41,14 +43,16 @@ public function deserializeInt(mixed $decoded, Field $field): int|DeformatterRes
return (int)($decoded[$field->serializedName]);
}

public function deserializeFloat(mixed $decoded, Field $field): float|DeformatterResult
public function deserializeFloat(mixed $decoded, Field $field): float|DeformatterResult|null
{
if (!isset($decoded[$field->serializedName])) {
if (!array_key_exists($field->serializedName, $decoded)) {
return DeformatterResult::Missing;
}

$value = $decoded[$field->serializedName];

if ($field->strict) {
if (!(is_float($decoded[$field->serializedName]) || is_int($decoded[$field->serializedName]))) {
if (!is_int($value) && !is_float($value) && !($field->nullable && is_null($value))) {
throw TypeMismatch::create($field->serializedName, 'float', \get_debug_type($decoded[$field->serializedName]));
}
return $decoded[$field->serializedName];
Expand All @@ -58,14 +62,16 @@ public function deserializeFloat(mixed $decoded, Field $field): float|Deformatte
return (float)($decoded[$field->serializedName]);
}

public function deserializeBool(mixed $decoded, Field $field): bool|DeformatterResult
public function deserializeBool(mixed $decoded, Field $field): bool|DeformatterResult|null
{
if (!isset($decoded[$field->serializedName])) {
if (!array_key_exists($field->serializedName, $decoded)) {
return DeformatterResult::Missing;
}

$value = $decoded[$field->serializedName];

if ($field->strict) {
if (!is_bool($decoded[$field->serializedName])) {
if (!is_bool($value) && !($field->nullable && is_null($value))) {
throw TypeMismatch::create($field->serializedName, 'bool', \get_debug_type($decoded[$field->serializedName]));
}
return $decoded[$field->serializedName];
Expand All @@ -75,21 +81,23 @@ public function deserializeBool(mixed $decoded, Field $field): bool|DeformatterR
return (bool)($decoded[$field->serializedName]);
}

public function deserializeString(mixed $decoded, Field $field): string|DeformatterResult
public function deserializeString(mixed $decoded, Field $field): string|DeformatterResult|null
{
if (!isset($decoded[$field->serializedName])) {
if (!array_key_exists($field->serializedName, $decoded)) {
return DeformatterResult::Missing;
}

$value = $decoded[$field->serializedName];

if ($field->strict) {
if (!is_string($decoded[$field->serializedName])) {
throw TypeMismatch::create($field->serializedName, 'string', \get_debug_type($decoded[$field->serializedName]));
if (!is_string($value) && !($field->nullable && is_null($value))) {
throw TypeMismatch::create($field->serializedName, 'string', \get_debug_type($value));
}
return $decoded[$field->serializedName];
return $value;
}

// Weak mode.
return (string)($decoded[$field->serializedName]);
return (string)($value);
}

public function deserializeNull(mixed $decoded, Field $field): ?DeformatterResult
Expand Down Expand Up @@ -211,15 +219,15 @@ protected function upcastArray(array $data, Deserializer $deserializer, ?string
* @param Deserializer $deserializer
* @return array<string, mixed>|DeformatterResult
*/
public function deserializeObject(mixed $decoded, Field $field, Deserializer $deserializer): array|DeformatterResult
public function deserializeObject(mixed $decoded, Field $field, Deserializer $deserializer): array|DeformatterResult|null
{
$candidateNames = [$field->serializedName, ...$field->alias];

$key = pipe($candidateNames,
first(static fn (string $name): bool => isset($decoded[$name]))
);

if (!isset($decoded[$key])) {
if (!array_key_exists($key, $decoded)) {
return DeformatterResult::Missing;
}

Expand Down Expand Up @@ -252,7 +260,7 @@ public function deserializeObject(mixed $decoded, Field $field, Deserializer $de
} else {
$key = pipe(
$propField->alias,
first(fn(string $name): bool => isset($data[$name])),
first(fn(string $name): bool => array_key_exists($name, $data)),
);
$ret[$propField->serializedName] = $key
? $deserializer->deserialize($data, $propField->with(serializedName: $key))
Expand Down
14 changes: 7 additions & 7 deletions src/Formatter/Deformatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ public function deserializeInitialize(
Deserializer $deserializer
): mixed;

public function deserializeInt(mixed $decoded, Field $field): int|DeformatterResult;
public function deserializeInt(mixed $decoded, Field $field): int|DeformatterResult|null;

public function deserializeFloat(mixed $decoded, Field $field): float|DeformatterResult;
public function deserializeFloat(mixed $decoded, Field $field): float|DeformatterResult|null;

public function deserializeBool(mixed $decoded, Field $field): bool|DeformatterResult;
public function deserializeBool(mixed $decoded, Field $field): bool|DeformatterResult|null;

public function deserializeString(mixed $decoded, Field $field): string|DeformatterResult;
public function deserializeString(mixed $decoded, Field $field): string|DeformatterResult|null;

public function deserializeNull(mixed $decoded, Field $field): ?DeformatterResult;
public function deserializeNull(mixed $decoded, Field $field): DeformatterResult|null;

/**
* @param mixed $decoded
Expand All @@ -61,9 +61,9 @@ public function deserializeDictionary(mixed $decoded, Field $field, Deserializer
* @param mixed $decoded
* @param Field $field
* @param Deserializer $deserializer
* @return array<string, mixed>|DeformatterResult
* @return array<string, mixed>|DeformatterResult|null
*/
public function deserializeObject( mixed $decoded, Field $field, Deserializer $deserializer): array|DeformatterResult;
public function deserializeObject( mixed $decoded, Field $field, Deserializer $deserializer): array|DeformatterResult|null;

public function deserializeFinalize(mixed $decoded): void;
}
2 changes: 1 addition & 1 deletion src/PropertyHandler/DateTimeExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
{
$string = $deserializer->deformatter->deserializeString($source, $field);

if ($string === DeformatterResult::Missing) {
if ($string === DeformatterResult::Missing || $string === null) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/PropertyHandler/DateTimeZoneExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
{
$string = $deserializer->deformatter->deserializeString($source, $field);

if ($string instanceof DeformatterResult) {
if ($string instanceof DeformatterResult || $string === null) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/PropertyHandler/DictionaryExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
// We cannot easily tell them apart at the moment.
if ($typeField instanceof DictionaryField && $typeField->implodeOn) {
$val = $deserializer->deformatter->deserializeString($source, $field);
return $val === DeformatterResult::Missing ? null : $typeField->explode($val);
return $val === DeformatterResult::Missing || $val === null ? null : $typeField->explode($val);
}

return $deserializer->deformatter->deserializeDictionary($source, $field, $deserializer);
Expand Down
2 changes: 1 addition & 1 deletion src/PropertyHandler/ObjectImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
// Get the raw data as an array from the source.
$dict = $deserializer->deformatter->deserializeObject($source, $field, $deserializer);

if ($dict instanceof DeformatterResult) {
if ($dict instanceof DeformatterResult || $dict === null) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/PropertyHandler/SequenceExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function importValue(Deserializer $deserializer, Field $field, mixed $sou
// We cannot easily tell them apart at the moment.
if ($typeField instanceof SequenceField && $typeField->implodeOn) {
$val = $deserializer->deformatter->deserializeString($source, $field);
return $val === DeformatterResult::Missing ? null : $typeField->explode($val);
return $val === DeformatterResult::Missing || $val === null ? null : $typeField->explode($val);
}

return $deserializer->deformatter->deserializeSequence($source, $field, $deserializer);
Expand Down
3 changes: 2 additions & 1 deletion tests/ArrayBasedFormatterTestCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ protected function empty_values_validate(mixed $serialized): void

self::assertEquals('narf', $toTest['nonConstructorDefault']);
self::assertEquals('beep', $toTest['required']);
self::assertNull($toTest['requiredNullable']);
self::assertEquals('boop', $toTest['withDefault']);
self::assertArrayNotHasKey('nullableUninitialized', $toTest);
self::assertArrayNotHasKey('uninitialized', $toTest);
Expand Down Expand Up @@ -427,7 +428,7 @@ public function class_level_renaming_applies_validate(mixed $serialized): void
self::assertArrayHasKey('the_number', $toTest);
}

public function null_stuff_validate(mixed $serialized): void
public function null_properties_are_allowed_validate(mixed $serialized): void
{
$toTest = $this->arrayify($serialized);

Expand Down
1 change: 1 addition & 0 deletions tests/Records/EmptyData.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class EmptyData

public function __construct(
public string $required,
public ?string $requiredNullable,
public string $withDefault = 'boop',
public ?string $nullable = null,
public readonly ?string $roNullable = null,
Expand Down
2 changes: 1 addition & 1 deletion tests/SerdeTestCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ public function empty_values(): void
{
$s = new SerdeCommon(formatters: $this->formatters);

$data = new EmptyData('beep');
$data = new EmptyData('beep', null);

$serialized = $s->serialize($data, $this->format);

Expand Down

0 comments on commit f3c429b

Please sign in to comment.