Skip to content

Commit

Permalink
fix: properly format empty object with JSON normalizer
Browse files Browse the repository at this point in the history
Objects that yield no value or have no property will now properly be
formatted into `{}` instead of `[]`.
  • Loading branch information
romm committed Jul 22, 2024
1 parent 69e0e3a commit 5ebcb27
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/Normalizer/ArrayNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CuyZ\Valinor\Normalizer;

use CuyZ\Valinor\Normalizer\Transformer\EmptyObject;
use CuyZ\Valinor\Normalizer\Transformer\RecursiveTransformer;

use function array_map;
Expand Down Expand Up @@ -38,6 +39,8 @@ private function normalizeIterator(mixed $value): mixed
}

$value = array_map($this->normalizeIterator(...), $value);
} elseif ($value instanceof EmptyObject) {
return [];
}

return $value;
Expand Down
3 changes: 3 additions & 0 deletions src/Normalizer/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace CuyZ\Valinor\Normalizer\Formatter;

use CuyZ\Valinor\Normalizer\Formatter\Exception\CannotFormatInvalidTypeToJson;
use CuyZ\Valinor\Normalizer\Transformer\EmptyObject;
use Generator;

use function array_is_list;
Expand Down Expand Up @@ -41,6 +42,8 @@ public function format(mixed $value): void
* tools understand that JSON_THROW_ON_ERROR is always set.
*/
$this->write(json_encode($value, $this->jsonEncodingOptions));
} elseif ($value instanceof EmptyObject) {
$this->write('{}');
} elseif (is_iterable($value)) {
// Note: when a generator is formatted, it is considered as a list
// if its first key is 0. This is done early because the first JSON
Expand Down
13 changes: 13 additions & 0 deletions src/Normalizer/Transformer/EmptyObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer\Transformer;

use CuyZ\Valinor\Utility\IsSingleton;

/** @internal */
final class EmptyObject
{
use IsSingleton;
}
17 changes: 15 additions & 2 deletions src/Normalizer/Transformer/RecursiveTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use function is_a;
use function is_array;
use function is_iterable;
use function is_object;

/** @internal */
final class RecursiveTransformer
Expand Down Expand Up @@ -105,11 +106,17 @@ private function defaultTransformer(mixed $value, WeakMap $references): mixed
);
}

return (function () use ($value, $references) {
$result = (function () use ($value, $references) {
foreach ($value as $key => $item) {
yield $key => $this->doTransform($item, $references);
}
})();

if (! $result->valid()) {
return EmptyObject::get();
}

return $result;
}

if (is_object($value) && ! $value instanceof Closure) {
Expand All @@ -126,9 +133,15 @@ private function defaultTransformer(mixed $value, WeakMap $references): mixed
}

if ($value::class === stdClass::class) {
$result = (array)$value;

if ($result === []) {
return EmptyObject::get();
}

return array_map(
fn (mixed $value) => $this->doTransform($value, $references),
(array)$value
$result
);
}

Expand Down
26 changes: 26 additions & 0 deletions tests/Integration/Normalizer/NormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,32 @@ public function __construct(
'transformerAttributes' => [],
'jsonEncodingOptions' => JSON_HEX_AMP,
];

yield 'stdClass with no property' => [
'input' => new stdClass(),
'expected array' => [],
'expected_json' => '{}',
];

yield 'ArrayObject with no property' => [
'input' => new ArrayObject(),
'expected array' => [],
'expected_json' => '{}',
];

yield 'iterable class with no property' => [
'input' => new class () implements IteratorAggregate {
public function getIterator(): Traversable
{
// @phpstan-ignore-next-line / Empty array is here on purpose
foreach ([] as $value) {
yield $value;
}
}
},
'expected array' => [],
'expected_json' => '{}',
];
}

public function test_generator_of_scalar_yields_expected_array(): void
Expand Down

0 comments on commit 5ebcb27

Please sign in to comment.