diff --git a/src/Client.php b/src/Client.php index 8c4c153..4ffc265 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1694,6 +1694,8 @@ public function toObject(array $dict): stdClass /** * Convert an object (stdClass) to an assoc array. * + * A nested object with no properties stays a stdClass, so it re-encodes as {} and not as []. + * * @param mixed $obj * @return array|null */ @@ -1703,18 +1705,25 @@ public function toArray(mixed $obj): ?array return null; } - if (is_object($obj) || is_array($obj)) { - $ret = (array)$obj; - foreach ($ret as $key => $item) { - if ($item instanceof stdClass || is_array($item)) { - $ret[$key] = $this->toArray($item); - } + if (!is_object($obj) && !is_array($obj)) { + return [$obj]; + } + + $ret = (array)$obj; + + foreach ($ret as $key => $item) { + if ($item instanceof stdClass) { + $ret[$key] = (array)$item === [] ? $item : $this->toArray($item); + + continue; } - return $ret; + if (is_array($item)) { + $ret[$key] = $this->toArray($item); + } } - return [$obj]; + return $ret; } private function cleanFilters($filters): array diff --git a/tests/MongoTest.php b/tests/MongoTest.php index afc073c..f69f481 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -393,6 +393,57 @@ public function testToArrayNestedConversion() self::assertEquals([42], $client->toArray(42)); } + public function testEmptyObjectSurvivesToArray() + { + $client = $this->getDatabase(); + + $created = $client->insert('movies_empty_object', [ + '_id' => 'empty-object-1', + 'empty' => new \stdClass(), + 'inner' => (object)['nested' => new \stdClass()], + 'list' => [new \stdClass(), (object)['x' => 1]], + 'filled' => (object)['a' => 1], + 'emptyList' => [], + ]); + + self::assertSame('{}', json_encode($created['empty']), 'insert() response flattened an empty object'); + self::assertSame('{"nested":{}}', json_encode($created['inner']), 'insert() response flattened a nested empty object'); + self::assertSame('[{},{"x":1}]', json_encode($created['list']), 'insert() response flattened an empty object inside a list'); + self::assertSame('{"a":1}', json_encode($created['filled'])); + self::assertSame('[]', json_encode($created['emptyList']), 'insert() response turned an empty array into an object'); + + self::assertIsArray($created['inner'], 'non-empty objects must still be associative arrays'); + self::assertSame(['a' => 1], $created['filled'], 'non-empty objects must still be associative arrays'); + + $batch = $client->insertMany('movies_empty_object', [[ + '_id' => 'empty-object-2', + 'empty' => new \stdClass(), + 'inner' => (object)['nested' => new \stdClass()], + 'list' => [new \stdClass(), (object)['x' => 1]], + ]]); + + self::assertSame('{}', json_encode($batch[0]['empty']), 'insertMany() response flattened an empty object'); + self::assertSame('{"nested":{}}', json_encode($batch[0]['inner']), 'insertMany() response flattened a nested empty object'); + self::assertSame('[{},{"x":1}]', json_encode($batch[0]['list']), 'insertMany() response flattened an empty object inside a list'); + + $read = $client->toArray($client->find('movies_empty_object', ['_id' => 'empty-object-1'])->cursor->firstBatch[0]); + + self::assertSame('{}', json_encode($read['empty']), 'read path flattened an empty object'); + self::assertSame('{"nested":{}}', json_encode($read['inner']), 'read path flattened a nested empty object'); + self::assertSame('[{},{"x":1}]', json_encode($read['list']), 'read path flattened an empty object inside a list'); + self::assertSame('{"a":1}', json_encode($read['filled'])); + self::assertSame('[]', json_encode($read['emptyList']), 'read path turned an empty array into an object'); + + $last = $client->lastDocument('movies_empty_object'); + + self::assertSame('{}', json_encode($last['empty']), 'lastDocument() flattened an empty object'); + self::assertSame('{"nested":{}}', json_encode($last['inner']), 'lastDocument() flattened a nested empty object'); + + self::assertSame([], $client->toArray(new \stdClass()), 'toArray() returns ?array, so only nested values change shape'); + + $client->dropCollection('movies_empty_object'); + } + public function testCountMethod() { $collectionName = 'count_test';