Skip to content

Commit

Permalink
feat: handle JSON_PRETTY_PRINT option with the JSON normalizer
Browse files Browse the repository at this point in the history
Formats the JSON with whitespaces and line breaks.

```php
$input = [
    'value' => 'foo',
    'list' => [
        'foo',
        42,
        ['sub']
    ],
    'associative' => [
        'value' => 'foo',
        'sub' => [
            'string' => 'foo',
            'integer' => 42,
        ],
    ],
];

(new \CuyZ\Valinor\MapperBuilder())
    ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
    ->withOptions(\JSON_PRETTY_PRINT)
    ->normalize($input);

// Result:
// {
//     "value": "foo",
//     "list": [
//         "foo",
//         42,
//         [
//             "sub"
//         ]
//     ],
//     "associative": {
//         "value": "foo",
//         "sub": {
//             "string": "foo",
//             "integer": 42
//         }
//     }
// }
  • Loading branch information
romm committed Oct 27, 2024
1 parent 6e68d6f commit f1fb93a
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/pages/serialization/normalizing-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ representations ([see official doc for more information]):
- `JSON_INVALID_UTF8_IGNORE`
- `JSON_INVALID_UTF8_SUBSTITUTE`
- `JSON_NUMERIC_CHECK`
- `JSON_PRETTY_PRINT`
- `JSON_PRESERVE_ZERO_FRACTION`
- `JSON_UNESCAPED_LINE_TERMINATORS`
- `JSON_UNESCAPED_SLASHES`
Expand Down
34 changes: 29 additions & 5 deletions src/Normalizer/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use function json_encode;

use const JSON_FORCE_OBJECT;
use const JSON_THROW_ON_ERROR;

/** @internal */
final class JsonFormatter implements StreamFormatter
Expand All @@ -32,6 +31,11 @@ public function __construct(
) {}

public function format(mixed $value): void
{
$this->formatRecursively($value, 1);
}

private function formatRecursively(mixed $value, int $depth): void
{
if (is_null($value)) {
$this->write('null');
Expand Down Expand Up @@ -66,22 +70,42 @@ public function format(mixed $value): void
$this->write($isList ? '[' : '{');

foreach ($value as $key => $val) {
$chunk = '';

if (! $isFirst) {
$this->write(',');
$chunk = ',';
}

if ($this->jsonEncodingOptions & JSON_PRETTY_PRINT) {
$chunk .= PHP_EOL . str_repeat(' ', $depth);
}

$isFirst = false;

if (! $isList) {
$key = json_encode((string)$key, $this->jsonEncodingOptions);

$this->write($key . ':');
$chunk .= $key . ':';

if ($this->jsonEncodingOptions & JSON_PRETTY_PRINT) {
$chunk .= ' ';
}
}

$this->format($val);
$this->write($chunk);

$this->formatRecursively($val, $depth + 1);
}

$chunk = '';

if ($this->jsonEncodingOptions & JSON_PRETTY_PRINT) {
$chunk = PHP_EOL . str_repeat(' ', $depth - 1);
}

$this->write($isList ? ']' : '}');
$chunk .= $isList ? ']' : '}';

$this->write($chunk);
} else {
throw new CannotFormatInvalidTypeToJson($value);
}
Expand Down
1 change: 1 addition & 0 deletions src/Normalizer/JsonNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class JsonNormalizer implements Normalizer
| JSON_INVALID_UTF8_IGNORE
| JSON_INVALID_UTF8_SUBSTITUTE
| JSON_NUMERIC_CHECK
| JSON_PRETTY_PRINT
| JSON_PRESERVE_ZERO_FRACTION
| JSON_UNESCAPED_LINE_TERMINATORS
| JSON_UNESCAPED_SLASHES
Expand Down
61 changes: 55 additions & 6 deletions tests/Integration/Normalizer/NormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,61 @@ public function getIterator(): Traversable
'expected array' => [],
'expected_json' => '{}',
];

yield 'nested array with JSON_PRETTY_PRINT option' => [
'input' => [
'value' => 'foo',
'list' => [
'foo',
42,
['sub']
],
'associative' => [
'value' => 'foo',
'sub' => [
'string' => 'foo',
'integer' => 42,
],
],
],
'expected array' => [
'value' => 'foo',
'list' => [
'foo',
42,
['sub']
],
'associative' => [
'value' => 'foo',
'sub' => [
'string' => 'foo',
'integer' => 42,
],
],
],
'expected_json' => <<<JSON
{
"value": "foo",
"list": [
"foo",
42,
[
"sub"
]
],
"associative": {
"value": "foo",
"sub": {
"string": "foo",
"integer": 42
}
}
}
JSON,
'transformers' => [],
'transformerAttributes' => [],
'jsonEncodingOptions' => JSON_PRETTY_PRINT,
];
}

public function test_generator_of_scalar_yields_expected_array(): void
Expand Down Expand Up @@ -1176,12 +1231,6 @@ public function test_json_transformer_only_accepts_acceptable_json_options(): vo
{
$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PARTIAL_OUTPUT_ON_ERROR);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));

$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PRETTY_PRINT);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));

$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_PRETTY_PRINT);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));
}
}

Expand Down

0 comments on commit f1fb93a

Please sign in to comment.