Repository: JsonMapper/JsonMapper (php)

Issue:
Private properties of parent classes are not taken into account when mapping data.
**Describe the bug**
Private properties of parent classes are not taken into account when mapping data. E.g. with the following structure
```php
class B extends A {}

class A {
    private readonly string $name
    
    public setName(string $name): void
    {
        $this->name = $name;
    }

   public getname(string $name): string
    {
        return $this->name;
    }
}
```
The property `$name` isn't found during the mapping.

**To Reproduce**
See above structure, when mapping `{"name": "John Doe"}` onto a `class B` the `name` property isn't found and left 

**Expected behavior**
The `name` property should be mapped.

**Stacktrace**
N/A

**Environment (please complete the following information):**
 - PHP: 8.1
 
**Additional context**
Found by Thijs.


Pull request description:
fix: Support private properties from parent class in DocBlockAnnotations and TypedProperties
Fixes #158 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45066c2..ac5ca62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Fixed 
+- Support private properties from parent class in DocBlockAnnotations and TypedProperties [PR#161](https://github.com/JsonMapper/JsonMapper/pull/161)
 
 ## [2.17.0] - 2023-05-02
 ### Changed
diff --git a/src/Middleware/DocBlockAnnotations.php b/src/Middleware/DocBlockAnnotations.php
index 8c6e280..45031ba 100644
--- a/src/Middleware/DocBlockAnnotations.php
+++ b/src/Middleware/DocBlockAnnotations.php
@@ -45,10 +45,8 @@ class DocBlockAnnotations extends AbstractMiddleware
             return $this->cache->get($cacheKey);
         }
 
-        $properties = $object->getReflectedObject()->getProperties();
         $intermediatePropertyMap = new PropertyMap();
-
-        foreach ($properties as $property) {
+        foreach ($this->getObjectPropertiesIncludingParents($object) as $property) {
             $name = $property->getName();
             $docBlock = $property->getDocComment();
             if ($docBlock === false) {
@@ -129,4 +127,15 @@ class DocBlockAnnotations extends AbstractMiddleware
 
         return new AnnotationMap($var ?: null, [], null);
     }
+
+    /** @return \ReflectionProperty[] */
+    public function getObjectPropertiesIncludingParents(ObjectWrapper $object): array
+    {
+        $properties = [];
+        $reflectionClass = $object->getReflectedObject();
+        do {
+            $properties = array_merge($properties, $reflectionClass->getProperties());
+        } while ($reflectionClass = $reflectionClass->getParentClass());
+        return $properties;
+    }
 }
diff --git a/src/Middleware/TypedProperties.php b/src/Middleware/TypedProperties.php
index bc42a9d..602a0e8 100644
--- a/src/Middleware/TypedProperties.php
+++ b/src/Middleware/TypedProperties.php
@@ -44,10 +44,9 @@ class TypedProperties extends AbstractMiddleware
             return $this->cache->get($cacheKey);
         }
 
-        $reflectionProperties = $object->getReflectedObject()->getProperties();
         $intermediatePropertyMap = new PropertyMap();
 
-        foreach ($reflectionProperties as $reflectionProperty) {
+        foreach ($this->getObjectPropertiesIncludingParents($object) as $reflectionProperty) {
             $type = $reflectionProperty->getType();
 
             if ($type instanceof ReflectionNamedType) {
@@ -97,4 +96,15 @@ class TypedProperties extends AbstractMiddleware
 
         return $intermediatePropertyMap;
     }
+
+    /** @return \ReflectionProperty[] */
+    public function getObjectPropertiesIncludingParents(ObjectWrapper $object): array
+    {
+        $properties = [];
+        $reflectionClass = $object->getReflectedObject();
+        do {
+            $properties = array_merge($properties, $reflectionClass->getProperties());
+        } while ($reflectionClass = $reflectionClass->getParentClass());
+        return $properties;
+    }
 }


Test patch:
diff --git a/tests/Implementation/Php74/SimpleObject.php b/tests/Implementation/Php74/SimpleObject.php
new file mode 100644
index 0000000..d2b20b7
--- /dev/null
+++ b/tests/Implementation/Php74/SimpleObject.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Implementation\Php74;
+
+class SimpleObject
+{
+    private string $name;
+
+    public function __construct(string $name = '')
+    {
+        $this->name = $name;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setName(string $name): void
+    {
+        $this->name = $name;
+    }
+}
diff --git a/tests/Implementation/Php74/SimpleObjectExtension.php b/tests/Implementation/Php74/SimpleObjectExtension.php
new file mode 100644
index 0000000..6601c8c
--- /dev/null
+++ b/tests/Implementation/Php74/SimpleObjectExtension.php
@@ -0,0 +1,9 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Implementation\Php74;
+
+class SimpleObjectExtension extends SimpleObject
+{
+}
diff --git a/tests/Implementation/SimpleObjectExtension.php b/tests/Implementation/SimpleObjectExtension.php
new file mode 100644
index 0000000..730a9fb
--- /dev/null
+++ b/tests/Implementation/SimpleObjectExtension.php
@@ -0,0 +1,9 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Implementation;
+
+class SimpleObjectExtension extends SimpleObject
+{
+}
diff --git a/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php b/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php
new file mode 100644
index 0000000..7e811e5
--- /dev/null
+++ b/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Integration;
+
+use JsonMapper\JsonMapperBuilder;
+use JsonMapper\Tests\Implementation\SimpleObjectExtension;
+use JsonMapper\Tests\Implementation\Php74\SimpleObjectExtension as TypedSimpleObjectExtension;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @coversNothing
+ */
+class FeatureSupportsMappingToParentPrivateProperties extends TestCase
+{
+    public function testItCanMapAnObjectUsingPrivatePropertiesInTheParentClassUsingDocBlocks(): void
+    {
+        // Arrange
+        $mapper = JsonMapperBuilder::new()
+            ->withDocBlockAnnotationsMiddleware()
+            ->build();
+        $json = (object) ['name' => __METHOD__];
+
+        // Act
+        $object = $mapper->mapToClass($json, SimpleObjectExtension::class);
+
+        // Assert
+        self::assertSame(__METHOD__, $object->getName());
+    }
+
+    public function testItCanMapAnObjectUsingPrivatePropertiesInTheParentClassUsingTypedProperties(): void
+    {
+        // Arrange
+        $mapper = JsonMapperBuilder::new()
+            ->withTypedPropertiesMiddleware()
+            ->build();
+        $json = (object) ['name' => __METHOD__];
+
+        // Act
+        $object = $mapper->mapToClass($json, TypedSimpleObjectExtension::class);
+
+        // Assert
+        self::assertSame(__METHOD__, $object->getName());
+    }
+}