diff --git a/.fernignore b/.fernignore index 57a0d2f..a1fc620 100644 --- a/.fernignore +++ b/.fernignore @@ -1,6 +1,6 @@ # Specify files that shouldn't be modified by Fern LICENSE.md CONTRIBUTING.md - -# nullable DateTime fix src/Core/Json/JsonSerializableType.php +src/Types/PushPayLinkRequest.php +tests/Core/Json/NullPropertyTest.php diff --git a/src/Core/Json/JsonSerializableType.php b/src/Core/Json/JsonSerializableType.php index 445d0bb..bee9dc7 100644 --- a/src/Core/Json/JsonSerializableType.php +++ b/src/Core/Json/JsonSerializableType.php @@ -39,12 +39,12 @@ public function toJson(): string } /** - * Serializes the object to an array. + * Serializes the object to a JSON-ready value. * - * @return mixed[] Array representation of the object. + * @return array|\stdClass * @throws JsonException If serialization fails. */ - public function jsonSerialize(): array + public function jsonSerialize(): array|\stdClass { $result = []; $reflectionClass = new \ReflectionClass($this); @@ -65,21 +65,24 @@ public function jsonSerialize(): array } // Handle Union annotations + $alreadySerialized = false; $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; if ($unionTypeAttr) { $unionType = $unionTypeAttr->newInstance(); $value = JsonSerializer::serializeUnion($value, $unionType); + $alreadySerialized = true; } // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { + if (!$alreadySerialized && $arrayTypeAttr && is_array($value)) { $arrayType = $arrayTypeAttr->newInstance()->type; $value = JsonSerializer::serializeArray($value, $arrayType); + $alreadySerialized = true; } // Handle object - if (is_object($value)) { + if (!$alreadySerialized && is_object($value)) { $value = JsonSerializer::serializeObject($value); } @@ -88,6 +91,10 @@ public function jsonSerialize(): array $result[$jsonKey] = $value; } } + // Empty object models must serialize to {} (not []) + if ($result === []) { + return new \stdClass(); + } return $result; } diff --git a/src/Types/PushPayLinkRequest.php b/src/Types/PushPayLinkRequest.php index 51f1b8c..d91240e 100644 --- a/src/Types/PushPayLinkRequest.php +++ b/src/Types/PushPayLinkRequest.php @@ -134,16 +134,22 @@ public function jsonSerialize(): array $result['channel'] = $this->channel; $base = parent::jsonSerialize(); - $result = array_merge($base, $result); + if (is_array($base)) { + $result = array_merge($base, $result); + } switch ($this->channel) { case 'email': $value = $this->asEmail()->jsonSerialize(); - $result = array_merge($value, $result); + if (is_array($value)) { + $result = array_merge($value, $result); + } break; case 'sms': $value = $this->asSms()->jsonSerialize(); - $result = array_merge($value, $result); + if (is_array($value)) { + $result = array_merge($value, $result); + } break; case '_unknown': default: @@ -152,9 +158,15 @@ public function jsonSerialize(): array } if ($this->value instanceof JsonSerializableType) { $value = $this->value->jsonSerialize(); - $result = array_merge($value, $result); + if (is_array($value)) { + $result = array_merge($value, $result); + } } elseif (is_array($this->value)) { $result = array_merge($this->value, $result); + } elseif ($this->value instanceof \stdClass) { + /** @var array $normalized */ + $normalized = (array) $this->value; + $result = array_merge($normalized, $result); } } @@ -190,7 +202,9 @@ public static function jsonDeserialize(array $data): static case '_unknown': default: $args['channel'] = '_unknown'; - $args['value'] = $data; + /** @var array $unknown */ + $unknown = $data; + $args['value'] = $unknown; } // @phpstan-ignore-next-line diff --git a/tests/Core/Json/NullPropertyTest.php b/tests/Core/Json/NullPropertyTest.php index 8f8ee6c..c44668c 100644 --- a/tests/Core/Json/NullPropertyTest.php +++ b/tests/Core/Json/NullPropertyTest.php @@ -46,6 +46,7 @@ public function testNullPropertiesAreOmitted(): void ); $serialized = $object->jsonSerialize(); + $this->assertIsArray($serialized, 'Serialized result should be an array.'); $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.');