Skip to content

Commit

Permalink
Merge pull request #5 from andreypostal/enums-support
Browse files Browse the repository at this point in the history
Enums support
  • Loading branch information
andreypostal authored Aug 20, 2024
2 parents 042e672 + 574455c commit 8e52941
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 16 deletions.
57 changes: 42 additions & 15 deletions src/JsonHydratorTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use JsonException;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;

trait JsonHydratorTrait
{
/**
* @throws JsonException
* @throws ReflectionException
*/
public function hydrateObjectImmutable(string|array $json, object $obj): object
{
Expand All @@ -21,6 +23,7 @@ public function hydrateObjectImmutable(string|array $json, object $obj): object

/**
* @throws JsonException
* @throws ReflectionException
*/
public function hydrateObject(string|array $json, object $obj): object
{
Expand All @@ -39,6 +42,7 @@ public function hydrateObject(string|array $json, object $obj): object

/**
* @throws JsonException
* @throws ReflectionException
*/
private function processClass(ReflectionClass $class, array $jsonArr): array
{
Expand All @@ -53,6 +57,7 @@ private function processClass(ReflectionClass $class, array $jsonArr): array

/**
* @throws JsonException
* @throws ReflectionException
*/
private function processProperty(ReflectionProperty $property, array $jsonArr, bool $skipAttributeCheck): mixed
{
Expand All @@ -70,26 +75,48 @@ private function processProperty(ReflectionProperty $property, array $jsonArr, b
}

if ($property->getType()?->isBuiltin()) {
if ($item->type !== null && $property->getType()?->getName() === 'array') {
$output = [];
$classExists = class_exists($item->type);
foreach ($jsonArr[$key] ?? [] as $k => $v) {
$value = $v;
if ($classExists) {
$value = $this->hydrateObject($v, new $item->type);
} elseif (gettype($v) !== $item->type) {
throw new LogicException(sprintf('expected array with items of type <%s> but found <%s>', $item->type, gettype($v)));
}
$output[$k] = $value;
return $this->handleBuiltin($jsonArr, $key, $property, $item);
}

return $this->handleCustomType($jsonArr[$key], $property->getType()?->getName());
}

/**
* @throws JsonException
* @throws ReflectionException
*/
private function handleBuiltin(array $jsonArr, string $key, ReflectionProperty $property, JsonItemAttribute $item): mixed
{
if ($item->type !== null && $property->getType()?->getName() === 'array') {
$output = [];
$classExists = class_exists($item->type);
foreach ($jsonArr[$key] ?? [] as $k => $v) {
$value = $v;
if ($classExists) {
$value = $this->handleCustomType($value, $item->type);
} elseif (gettype($v) !== $item->type) {
throw new LogicException(sprintf('expected array with items of type <%s> but found <%s>', $item->type, gettype($v)));
}
return $output;
$output[$k] = $value;
}
return $jsonArr[$key] ?? $property->getDefaultValue();
return $output;
}
return $jsonArr[$key] ?? ($property->hasDefaultValue() ? $property->getDefaultValue() : null);
}

/**
* @throws ReflectionException
* @throws JsonException
*/
private function handleCustomType(mixed $value, string $type): mixed
{
$typeReflection = new ReflectionClass($type);
if ($typeReflection->isEnum()) {
return call_user_func($type.'::tryFrom', $value);
}
return $this->hydrateObject(
$jsonArr[$key],
new ($property->getType()?->getName())(),
$value,
new ($type)(),
);
}
}
54 changes: 53 additions & 1 deletion src/JsonSerializerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@

use Andrey\JsonHandler\Attributes\JsonItemAttribute;
use Andrey\JsonHandler\Attributes\JsonObjectAttribute;
use JsonException;
use ReflectionClass;
use ReflectionException;

trait JsonSerializerTrait
{
/**
* @throws ReflectionException
* @throws JsonException
*/
public function serialize(object $obj): array
{
$class = new ReflectionClass($obj);
if ($class->isEnum()) {
return $obj->value;
}

$skipAttributeCheck = ($class->getAttributes(JsonObjectAttribute::class)[0] ?? null) !== null;
$output = [];
$properties = $class->getProperties();
Expand All @@ -24,11 +34,53 @@ public function serialize(object $obj): array
$key = $item->key ?? $property->name;

if ($property->getType()?->isBuiltin()) {
$output[$key] = $property->getValue($obj);
$output[$key] = $this->handleArray($item, $property->getValue($obj));
continue;
}

$class = new ReflectionClass($property->getValue($obj));
if ($class->isEnum()) {
$output[$key] = $property->getValue($obj)->value;
continue;
}
$output[$key] = $this->serialize($property->getValue($obj));
}
return $output;
}

/**
* @param JsonItemAttribute $item
* @param mixed $value
* @return mixed
* @throws ReflectionException
* @throws JsonException
*
* @noinspection GetTypeMissUseInspection
*/
private function handleArray(JsonItemAttribute $item, mixed $value): mixed
{
if (gettype($value) !== 'array') {
return $value;
}

if (!class_exists($item->type)) {
return $value;
}
$class = new ReflectionClass($item->type);
$isEnum = $class->isEnum();

return array_reduce(
array: $value,
callback: function(array $l, mixed $c) use ($isEnum): array {
if ($isEnum) {
$v = $c->value;
} else {
$v = $this->serialize($c);
}
$l[] = $v;
return $l;
},
initial: [],
);
}
}
48 changes: 48 additions & 0 deletions tests/HydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class HydratorTest extends TestCase
{
/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSimpleHydrate(): void
{
Expand All @@ -34,6 +35,7 @@ public function testSimpleHydrate(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testImmutableHydrate(): void
{
Expand All @@ -58,6 +60,7 @@ public function testImmutableHydrate(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithoutOptionalItems(): void
{
Expand All @@ -76,6 +79,7 @@ public function testHydrateWithoutOptionalItems(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithoutRequiredItem(): void
{
Expand All @@ -90,6 +94,7 @@ public function testHydrateWithoutRequiredItem(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithMultipleLevels(): void
{
Expand All @@ -106,6 +111,7 @@ public function testHydrateWithMultipleLevels(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithArray(): void
{
Expand All @@ -123,6 +129,7 @@ public function testHydrateWithArray(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithInvalidArray(): void
{
Expand All @@ -138,6 +145,7 @@ public function testHydrateWithInvalidArray(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithArrayOfObjects(): void
{
Expand All @@ -155,6 +163,7 @@ public function testHydrateWithArrayOfObjects(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testHydrateWithObjectAttr(): void
{
Expand All @@ -172,6 +181,7 @@ public function testHydrateWithObjectAttr(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testMixedObjectAndItemAttributes(): void
{
Expand All @@ -186,4 +196,42 @@ public function testMixedObjectAndItemAttributes(): void
$this->assertEquals(1.5, $obj->float);
$this->assertFalse($obj->bool);
}

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSimpleEnum(): void
{
$json = '{"id": "str", "enum": "aaa"}';
$obj = new WithEnumObject();

$handler = new JsonHandler();
$handler->hydrateObject($json, $obj);

$this->assertIsObject($obj->enum);
$this->assertEquals('aaa', $obj->enum->value);
$this->assertEquals('Aaa', $obj->enum->name);
}

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testArrayOfEnum(): void
{
$json = '{"id": "str", "enum": [ "aaa", "bbb", "abc" ]}';
$obj = new WithArrayOfEnumObject();

$handler = new JsonHandler();
$handler->hydrateObject($json, $obj);

$this->assertCount(3, $obj->enum);
$this->assertEquals('aaa', $obj->enum[0]->value);
$this->assertEquals('Aaa', $obj->enum[0]->name);
$this->assertEquals('bbb', $obj->enum[1]->value);
$this->assertEquals('Bbb', $obj->enum[1]->name);
$this->assertEquals('abc', $obj->enum[2]->value);
$this->assertEquals('Abc', $obj->enum[2]->name);
}
}
46 changes: 46 additions & 0 deletions tests/SerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public function testSimpleSerialize(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSerializeWithKeyModified(): void
{
Expand Down Expand Up @@ -53,6 +54,7 @@ public function testSerializeWithKeyModified(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSerializeMultiLevel(): void
{
Expand All @@ -75,6 +77,7 @@ public function testSerializeMultiLevel(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSerializeWithExtraItems(): void
{
Expand All @@ -95,6 +98,47 @@ public function testSerializeWithExtraItems(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSimpleEnum(): void
{
$obj = new WithEnumObject();

$handler = new JsonHandler();
$arr = $handler->serialize($obj);

$this->assertArrayHasKey('id', $arr);
$this->assertArrayHasKey('enum', $arr);
$this->assertIsString($arr['enum']);
$this->assertEquals('abc', $arr['enum']);

JsonHandler::Encode($arr);
}

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testArrayEnum(): void
{
$obj = new WithArrayOfEnumObject();

$handler = new JsonHandler();
$arr = $handler->serialize($obj);

$this->assertArrayHasKey('id', $arr);
$this->assertArrayHasKey('enum', $arr);
$this->assertIsArray($arr['enum']);
$this->assertCount(2, $arr['enum']);
$this->assertEquals('abc', $arr['enum'][0]);
$this->assertEquals('aaa', $arr['enum'][1]);

JsonHandler::Encode($arr);
}

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSerializeWithObjectAttr(): void
{
Expand All @@ -103,6 +147,7 @@ public function testSerializeWithObjectAttr(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
public function testSerializeWithMixedAttrs(): void
{
Expand All @@ -111,6 +156,7 @@ public function testSerializeWithMixedAttrs(): void

/**
* @throws JsonException
* @throws ReflectionException
*/
private function assertSimpleSerializedObject(object $obj): void
{
Expand Down
3 changes: 3 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
require_once __DIR__ . '/utils/WithArrayOfChildObject.php';
require_once __DIR__ . '/utils/SimpleTestWithObjectAttr.php';
require_once __DIR__ . '/utils/MixedAttributesObject.php';
require_once __DIR__ . '/utils/EnumObject.php';
require_once __DIR__ . '/utils/WithArrayOfEnumObject.php';
require_once __DIR__ . '/utils/WithEnumObject.php';
Loading

0 comments on commit 8e52941

Please sign in to comment.