Repository: JsonMapper/JsonMapper (php)

Issue:
JsonMapper::mapObject should returned the mapped object
**Is your feature request related to a problem? Please describe.**
With the addition of Psalm annotations we have some form of generics (at least with regards to the auto completion.
It would be helpful to return the mapped object as other JSON mapping packages do so. This would make it a better drop-in replacement.

**Describe the solution you'd like**
Return the mapped object from `mapObject` and `mapObjectFromString` and use Psalm notations to ensure the IDE auto completion provides some valuable feedback.

**Describe alternatives you've considered**
None, leaving it as is could be fine but makes it less easier to provide JsonMapper as a drop-in replacement.

Pull request description:
Add returns and psalm typehints
This will fix #115 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 826deeb..29eecda 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 ### Added
 - Support was added for strict scalar casting [PR#119](https://github.com/JsonMapper/JsonMapper/pull/119) Thanks to [template-provider](https://github.com/template-provider) for reporting the issue
+- All **Map** functions now return the mapped object(s) and uses [Psalm](https://psalm.dev) to assist with autocompletion. [PR#122](https://github.com/JsonMapper/JsonMapper/pull/122)
 
 ## [2.9.1] - 2021-11-12
 ### Fixed
diff --git a/src/Handler/PropertyMapper.php b/src/Handler/PropertyMapper.php
index ffed0e6..795b048 100644
--- a/src/Handler/PropertyMapper.php
+++ b/src/Handler/PropertyMapper.php
@@ -198,11 +198,15 @@ class PropertyMapper
             return $this->classFactoryRegistry->create($type->getType(), $value);
         }
 
-        if ($type->isArray()) {
+        if ($type->isArray() && (class_exists($type->getType()) || interface_exists($type->getType()))) {
             return $this->mapToArrayOfObjects($type->getType(), $value, $mapper);
         }
 
-        return $this->mapToObject($type->getType(), $value, $mapper);
+        if (class_exists($type->getType()) || interface_exists($type->getType())) {
+            return $this->mapToObject($type->getType(), $value, $mapper);
+        }
+
+        throw new \Exception("Unable to map to {$type->getType()}");
     }
 
     /**
@@ -293,8 +297,10 @@ class PropertyMapper
     }
 
     /**
+     * @template T
+     * @psalm-param class-string<T> $type
      * @param mixed $value
-     * @return object[]
+     * @return array<int, T>
      */
     private function mapToArrayOfObjects(string $type, $value, JsonMapperInterface $mapper): array
     {
@@ -307,8 +313,10 @@ class PropertyMapper
     }
 
     /**
+     * @template T
+     * @psalm-param class-string<T> $type
      * @param mixed $value
-     * @return object
+     * @return T
      */
     private function resolveUnInstantiableType(string $type, $value, JsonMapperInterface $mapper)
     {
diff --git a/src/JsonMapper.php b/src/JsonMapper.php
index 9a35f8b..3b25c24 100644
--- a/src/JsonMapper.php
+++ b/src/JsonMapper.php
@@ -90,8 +90,7 @@ class JsonMapper implements JsonMapperInterface
         return $this;
     }
 
-    /** @param object $object */
-    public function mapObject(\stdClass $json, $object): void
+    public function mapObject(\stdClass $json, $object)
     {
         if (! \is_object($object)) {
             throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object');
@@ -101,9 +100,10 @@ class JsonMapper implements JsonMapperInterface
 
         $handler = $this->resolve();
         $handler($json, new ObjectWrapper($object), $propertyMap, $this);
+
+        return $object;
     }
 
-    /** @param object $object */
     public function mapArray(array $json, $object): array
     {
         if (! \is_object($object)) {
@@ -119,8 +119,7 @@ class JsonMapper implements JsonMapperInterface
         return $results;
     }
 
-    /** @param object $object */
-    public function mapObjectFromString(string $json, $object): void
+    public function mapObjectFromString(string $json, $object)
     {
         if (! \is_object($object)) {
             throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object');
@@ -133,9 +132,10 @@ class JsonMapper implements JsonMapperInterface
         }
 
         $this->mapObject($data, $object);
+
+        return $object;
     }
 
-    /** @param object $object */
     public function mapArrayFromString(string $json, $object): array
     {
         if (! \is_object($object)) {
diff --git a/src/JsonMapperBuilder.php b/src/JsonMapperBuilder.php
index 84f8285..173b070 100644
--- a/src/JsonMapperBuilder.php
+++ b/src/JsonMapperBuilder.php
@@ -21,9 +21,14 @@ use JsonMapper\Middleware\TypedProperties;
 use Psr\Log\LoggerInterface;
 use Psr\SimpleCache\CacheInterface;
 
+/**
+ * @template T of \JsonMapper\JsonMapperInterface
+ */
 class JsonMapperBuilder
 {
-    /** @var string string */
+    /**
+     * @psalm-var class-string<T>
+     */
     protected $jsonMapperClassName = JsonMapper::class;
     /** @var PropertyMapper */
     protected $propertyMapper;
@@ -59,7 +64,7 @@ class JsonMapperBuilder
         return $mapper;
     }
 
-    /** @psalm-param class-string $jsonMapperClassName */
+    /** @psalm-param class-string<T> $jsonMapperClassName */
     public function withJsonMapperClassName(string $jsonMapperClassName): JsonMapperBuilder
     {
         $reflectedClass = new \ReflectionClass($jsonMapperClassName);
diff --git a/src/JsonMapperInterface.php b/src/JsonMapperInterface.php
index 113e66c..b6af35d 100644
--- a/src/JsonMapperInterface.php
+++ b/src/JsonMapperInterface.php
@@ -20,15 +20,31 @@ interface JsonMapperInterface
 
     public function removeByName(string $remove): self;
 
-    /** @param object $object */
-    public function mapObject(\stdClass $json, $object): void;
-
-    /** @param object $object */
+    /**
+     * @template T of object
+     * @psalm-param T $object
+     * @return T
+     */
+    public function mapObject(\stdClass $json, $object);
+
+    /**
+     * @template T of object
+     * @psalm-param T $object
+     * @return array<int, T>
+     */
     public function mapArray(array $json, $object): array;
 
-    /** @param object $object */
-    public function mapObjectFromString(string $json, $object): void;
-
-    /** @param object $object */
+    /**
+     * @template T of object
+     * @psalm-param T $object
+     * @return T
+     */
+    public function mapObjectFromString(string $json, $object);
+
+    /**
+     * @template T of object
+     * @psalm-param T $object
+     *  @return array<int, T>
+     */
     public function mapArrayFromString(string $json, $object): array;
 }
diff --git a/src/Wrapper/ObjectWrapper.php b/src/Wrapper/ObjectWrapper.php
index 60e3775..56a6285 100644
--- a/src/Wrapper/ObjectWrapper.php
+++ b/src/Wrapper/ObjectWrapper.php
@@ -10,7 +10,7 @@ class ObjectWrapper
 {
     /** @var object */
     private $object;
-    /** @var \ReflectionClass */
+    /** @var \ReflectionClass|null */
     private $reflectedObject;
 
     /** @param object $object */


Test patch:
diff --git a/tests/Unit/Handler/PropertyMapperTest.php b/tests/Unit/Handler/PropertyMapperTest.php
index 0401594..04f3498 100644
--- a/tests/Unit/Handler/PropertyMapperTest.php
+++ b/tests/Unit/Handler/PropertyMapperTest.php
@@ -715,7 +715,8 @@ class PropertyMapperTest extends TestCase
         $propertyMapper = new PropertyMapper();
         $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache()));
 
-        $this->expectException(TypeError::class);
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unable to map to \Some\Non\Existing\Class');
         $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper);
     }