diff --git a/README.md b/README.md index 49c7628..021326c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,9 @@ composer require andreypostal/json-handler-php When creating your **Value Objects** that represent a **JSON entity** you just need to add the ``JsonItemAttribute`` to each property that will be present in the JSON. ```php -use \Andrey\JsonHandler\JsonItemAttribute; +use \Andrey\JsonHandler\Attributes\JsonItemAttribute; +// { "id": 123, "name": "my name" } class MyObject { #[JsonItemAttribute] public int $id; @@ -27,11 +28,24 @@ class MyObject { } ``` +In the case of the entire object being a JsonObject with a direct 1:1 match (or perfect mirror of the keys), you can use the ``JsonObjectAttribute`` +```php +use \Andrey\JsonHandler\Attributes\JsonObjectAttribute; + +// { "id": 123, "name": "my name" } +#[JsonObjectAttribute] +class MyObject { + public int $id; + public string $name; +} +``` + If your **Value Object** has some property that **won't be present** in the JSON, you can just omit the attribute for it and the other ones will be processed normally. ```php -use \Andrey\JsonHandler\JsonItemAttribute; +use \Andrey\JsonHandler\Attributes\JsonItemAttribute; +// { "id": 123 } class MyObject { #[JsonItemAttribute] public int $id; @@ -41,21 +55,25 @@ class MyObject { In case the items are required to exist in the JSON being processed, you must add the required flag in the attribute. ```php -use \Andrey\JsonHandler\JsonItemAttribute; +use \Andrey\JsonHandler\Attributes\JsonItemAttribute; +// { "id": 123 } or { "id": 123, "name": "my name" } class MyObject { #[JsonItemAttribute(required: true)] public int $id; + #[JsonItemAttribute] + public string $name; } ``` When some of the keys in your JSON are different from your object, you can include the JSON key in the attribute. ```php -use \Andrey\JsonHandler\JsonItemAttribute; +use \Andrey\JsonHandler\Attributes\JsonItemAttribute; +// { "customer_name": "the customer name" } class MyObject { #[JsonItemAttribute(key: 'customer_name')] - public name $name; + public string $name; } ``` @@ -65,6 +83,7 @@ This will work as a hint so the hydrator can instantiate the appropriate object. use \Andrey\JsonHandler\JsonItemAttribute; use \MyNamespace\MyOtherObj; +// { "list": [ { "key": "value" } ] } class MyObject { /** @var MyOtherObj[] */ #[JsonItemAttribute(type: MyOtherObj::class)] diff --git a/src/JsonItemAttribute.php b/src/Attributes/JsonItemAttribute.php similarity index 83% rename from src/JsonItemAttribute.php rename to src/Attributes/JsonItemAttribute.php index 61825b8..e82ae5f 100644 --- a/src/JsonItemAttribute.php +++ b/src/Attributes/JsonItemAttribute.php @@ -1,5 +1,5 @@ getAttributes(JsonObjectAttribute::class)[0] ?? null) !== null; $output = []; $properties = $class->getProperties(); foreach ($properties as $property) { - $output[$property->getName()] = $this->processProperty($property, $jsonArr); + $output[$property->getName()] = $this->processProperty($property, $jsonArr, $skipAttributeCheck); } return $output; } @@ -51,16 +54,16 @@ private function processClass(ReflectionClass $class, array $jsonArr): array /** * @throws JsonException */ - private function processProperty(ReflectionProperty $property, array $jsonArr): mixed + private function processProperty(ReflectionProperty $property, array $jsonArr, bool $skipAttributeCheck): mixed { $attributes = $property->getAttributes(JsonItemAttribute::class); $attr = $attributes[0] ?? null; - if ($attr === null) { + if ($attr === null && !$skipAttributeCheck) { return null; } /** @var JsonItemAttribute $item */ - $item = $attr->newInstance(); + $item = $skipAttributeCheck ? new JsonItemAttribute() : $attr->newInstance(); $key = $item->key ?? $property->getName(); if ($item->required && !array_key_exists($key, $jsonArr)) { throw new InvalidArgumentException(sprintf('required item <%s> not found', $key)); diff --git a/src/JsonSerializerTrait.php b/src/JsonSerializerTrait.php index 35d17be..88844fc 100644 --- a/src/JsonSerializerTrait.php +++ b/src/JsonSerializerTrait.php @@ -1,6 +1,8 @@ getAttributes(JsonObjectAttribute::class)[0] ?? null) !== null; $output = []; $properties = $class->getProperties(); foreach ($properties as $property) { $attributes = $property->getAttributes(JsonItemAttribute::class); $attr = $attributes[0] ?? null; - if ($attr === null) { + if ($attr === null && !$skipAttributeCheck) { continue; } /** @var JsonItemAttribute $item */ - $item = $attr->newInstance(); + $item = $skipAttributeCheck ? new JsonItemAttribute() : $attr->newInstance(); $key = $item->key ?? $property->name; if ($property->getType()?->isBuiltin()) { diff --git a/tests/HydratorTest.php b/tests/HydratorTest.php index 326e78b..e529ee7 100644 --- a/tests/HydratorTest.php +++ b/tests/HydratorTest.php @@ -1,8 +1,9 @@ assertIsObject($obj->children[0]); $this->assertEquals('abc', $obj->children[0]->string); } + + /** + * @throws JsonException + */ + public function testHydrateWithObjectAttr(): void + { + $json = '{"string": "str", "int": 1, "float": 1.50, "bool": false}'; + $obj = new SimpleTestWithObjectAttr(); + + $handler = new JsonHandler(); + $handler->hydrateObject($json, $obj); + + $this->assertEquals('str', $obj->string); + $this->assertEquals(1, $obj->int); + $this->assertEquals(1.5, $obj->float); + $this->assertFalse($obj->bool); + } } diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index ddcfa14..356e824 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -1,7 +1,8 @@ serialize($obj); - - $this->assertArrayHasKey('string', $arr); - $this->assertArrayHasKey('int', $arr); - $this->assertArrayHasKey('float', $arr); - $this->assertArrayHasKey('bool', $arr); - - $this->assertIsBool($arr['bool']); - $this->assertTrue($arr['bool']); - - $this->assertIsInt($arr['int']); - $this->assertEquals(11, $arr['int']); - - $this->assertIsFloat($arr['float']); - $this->assertEquals(11.50, $arr['float']); - - $this->assertIsString($arr['string']); - $this->assertEquals('string', $arr['string']); - - // No exceptions - $json = JsonHandler::Encode($arr); - $this->assertStringContainsString('float', $json); - $this->assertStringContainsString('int', $json); - $this->assertStringContainsString('bool', $json); - $this->assertStringContainsString('string', $json); + $this->assertSimpleSerializedObject(new SimpleTestObject()); } /** @@ -117,4 +92,45 @@ public function testSerializeWithExtraItems(): void JsonHandler::Encode($arr); } + + /** + * @throws JsonException + */ + public function testSerializeWithObjectAttr(): void + { + $this->assertSimpleSerializedObject(new SimpleTestWithObjectAttr()); + } + + /** + * @throws JsonException + */ + private function assertSimpleSerializedObject(object $obj): void + { + $handler = new JsonHandler(); + $arr = $handler->serialize($obj); + + $this->assertArrayHasKey('string', $arr); + $this->assertArrayHasKey('int', $arr); + $this->assertArrayHasKey('float', $arr); + $this->assertArrayHasKey('bool', $arr); + + $this->assertIsBool($arr['bool']); + $this->assertTrue($arr['bool']); + + $this->assertIsInt($arr['int']); + $this->assertEquals(11, $arr['int']); + + $this->assertIsFloat($arr['float']); + $this->assertEquals(11.50, $arr['float']); + + $this->assertIsString($arr['string']); + $this->assertEquals('string', $arr['string']); + + // No exceptions + $json = JsonHandler::Encode($arr); + $this->assertStringContainsString('float', $json); + $this->assertStringContainsString('int', $json); + $this->assertStringContainsString('bool', $json); + $this->assertStringContainsString('string', $json); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6704a92..46fd167 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,4 +5,5 @@ require_once __DIR__ . '/utils/WithChildObject.php'; require_once __DIR__ . '/utils/SimpleTestWithArrayObject.php'; require_once __DIR__ . '/utils/WithArrayOfChildObject.php'; +require_once __DIR__ . '/utils/SimpleTestWithObjectAttr.php'; diff --git a/tests/utils/SimpleTestObject.php b/tests/utils/SimpleTestObject.php index 963ea7a..7a6728f 100644 --- a/tests/utils/SimpleTestObject.php +++ b/tests/utils/SimpleTestObject.php @@ -1,6 +1,6 @@