From 66a16b4203dceccc3d943a71ec5571161dd99c5e Mon Sep 17 00:00:00 2001 From: payablijah Date: Sun, 19 Jul 2026 23:53:47 -0400 Subject: [PATCH 1/5] updated json deserialization code --- .fernignore | 1 + src/Core/Json/JsonSerializableType.php | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.fernignore b/.fernignore index 33bb080..0c51fff 100644 --- a/.fernignore +++ b/.fernignore @@ -1,3 +1,4 @@ # Specify files that shouldn't be modified by Fern LICENSE.md CONTRIBUTING.md +src/Core/Json/JsonSerializableType.php diff --git a/src/Core/Json/JsonSerializableType.php b/src/Core/Json/JsonSerializableType.php index a555b67..b7f26a8 100644 --- a/src/Core/Json/JsonSerializableType.php +++ b/src/Core/Json/JsonSerializableType.php @@ -39,13 +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); foreach ($reflectionClass->getProperties() as $property) { @@ -88,6 +87,10 @@ public function jsonSerialize(): array $result[$jsonKey] = $value; } } + // Empty object models must serialize to {} (not []) + if ($result === []) { + return new \stdClass(); + } return $result; } From c56d4d56efd4d17a6cb63753d827e86e983023c7 Mon Sep 17 00:00:00 2001 From: payablijah Date: Sun, 19 Jul 2026 23:58:07 -0400 Subject: [PATCH 2/5] syntax fix --- src/Core/Json/JsonSerializableType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Json/JsonSerializableType.php b/src/Core/Json/JsonSerializableType.php index b7f26a8..c6e1c3f 100644 --- a/src/Core/Json/JsonSerializableType.php +++ b/src/Core/Json/JsonSerializableType.php @@ -45,6 +45,7 @@ public function toJson(): string * @throws JsonException If serialization fails. */ public function jsonSerialize(): array|\stdClass + { $result = []; $reflectionClass = new \ReflectionClass($this); foreach ($reflectionClass->getProperties() as $property) { From 04f201b612611e4312957477c072b859da17041b Mon Sep 17 00:00:00 2001 From: payablijah Date: Mon, 20 Jul 2026 00:07:28 -0400 Subject: [PATCH 3/5] fix: guard array_merge against stdClass in PushPayLinkRequest json serialization JsonSerializableType::jsonSerialize() returns array|stdClass, but PushPayLinkRequest passed the result directly to array_merge(), which only accepts arrays. This fixes the composer analyze (PHPStan) CI failure by normalizing to arrays before merging and tightening the unknown-branch typing in jsonSerialize()/jsonDeserialize(). Co-Authored-By: Claude Sonnet 5 --- src/Types/PushPayLinkRequest.php | 24 +++++++++++++++++++----- tests/Core/Json/NullPropertyTest.php | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) 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.'); From f23ae7d8ab6f788b33aa17b73b07253784537c7b Mon Sep 17 00:00:00 2001 From: payablijah Date: Mon, 20 Jul 2026 00:10:04 -0400 Subject: [PATCH 4/5] fix: prevent double-serialization of Union/ArrayType properties in jsonSerialize jsonSerialize() unconditionally re-passed already-serialized Union/ArrayType values through serializeObject(), which requires a JsonSerializable instance. An empty map or empty union member serializes to a plain stdClass, which isn't JsonSerializable, so re-processing it threw "Class stdClass must implement JsonSerializable." Skip the object-handling branch once a value has already been serialized by the Union or ArrayType path. Co-Authored-By: Claude Sonnet 5 --- src/Core/Json/JsonSerializableType.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Json/JsonSerializableType.php b/src/Core/Json/JsonSerializableType.php index 400a248..bee9dc7 100644 --- a/src/Core/Json/JsonSerializableType.php +++ b/src/Core/Json/JsonSerializableType.php @@ -65,21 +65,24 @@ public function jsonSerialize(): array|\stdClass } // 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); } From 8aef54ad847fe9b25ff9d5eec3fd758e1ca2104a Mon Sep 17 00:00:00 2001 From: payablijah Date: Mon, 20 Jul 2026 00:11:20 -0400 Subject: [PATCH 5/5] chore: add manually-fixed JSON files to fernignore PushPayLinkRequest.php and NullPropertyTest.php were hand-patched to fix PHPStan/PHPUnit failures; excluding them from Fern regeneration so the fixes aren't clobbered on the next generation run. Co-Authored-By: Claude Sonnet 5 --- .fernignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.fernignore b/.fernignore index 0c51fff..a1fc620 100644 --- a/.fernignore +++ b/.fernignore @@ -2,3 +2,5 @@ LICENSE.md CONTRIBUTING.md src/Core/Json/JsonSerializableType.php +src/Types/PushPayLinkRequest.php +tests/Core/Json/NullPropertyTest.php