Skip to content

Commit

Permalink
Improve JSONConverter public API
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Oct 7, 2024
1 parent 1f884ac commit b05034e
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 2 deletions.
26 changes: 24 additions & 2 deletions docs/9.0/converter/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ public JsonConverter::removeFlags(int ...$flag): self
This method sets the JSON flags to be used during conversion. The method handles all the
flags supported by PHP `json_encode` function.

If you prefer a more expressive way for setting the flags you can use `with*` and `without*` methods
whose name are derived from PHP JSON constants.

```php
$converter = JsonConverter::create()
->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT)
->removeFlags(JSON_HEX_QUOT);

//is equivalent to

$converter = JsonConverter::create()
->withPrettyPrint()
->withUnescapedSlashes()
->withForceObject()
->withoutHexQuot();
```

<p class="message-notice">Because we are converting one record at a time, the class always uses <code>JSON_THROW_ON_ERROR</code>
to stop the collection conversion. As such adding or removing the flag using the methods describe here before will
have no effect on its usage, the flag is <strong>ALWAYS</strong> set.</p>

### JsonConverter::depth

```php
Expand Down Expand Up @@ -52,7 +73,7 @@ public JsonConverter::formatter(?callback $formatter): mixed

This method allow to apply a callback prior to `json_encode` your collection individual item.
Since the encoder does not rely on PHP's `JsonSerializable` interface but on PHP's `iterable`
structure. The expected conversion may differ to what you expect. This callback allows you to
structure. The resulting conversion may differ to what you expect. This callback allows you to
specify how each item will be converted. The formatter should return a type that can be handled
by PHP `json_encode` function.

Expand Down Expand Up @@ -81,7 +102,8 @@ $document->setHeaderOffset(0);

CharsetConverter::addTo($document, 'iso-8859-15', 'utf-8');
$converter = JsonConverter::create()
->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES)
->withPrettyPrint()
->withUnescapedSlashes()
->depth(2)
->indentSize(2)
->formatter(function (array $row) {
Expand Down
92 changes: 92 additions & 0 deletions src/JsonConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace League\Csv;

use BadMethodCallException;
use Closure;
use Exception;
use InvalidArgumentException;
Expand All @@ -22,6 +23,26 @@
use SplFileInfo;
use SplFileObject;

use function array_filter;
use function array_keys;
use function array_reduce;
use function get_defined_constants;
use function is_resource;
use function is_string;
use function json_encode;
use function json_last_error;
use function lcfirst;
use function restore_error_handler;
use function set_error_handler;
use function str_repeat;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strtolower;
use function substr;
use function ucwords;

use const ARRAY_FILTER_USE_KEY;
use const JSON_ERROR_NONE;
use const JSON_FORCE_OBJECT;
use const JSON_PRETTY_PRINT;
Expand All @@ -30,6 +51,41 @@
/**
* Converts and store tabular data into a JSON string.
* @template T
*
* @method JsonConverter withHexTag() adds the JSON_HEX_TAG flag
* @method JsonConverter withoutHexTag() removes the JSON_HEX_TAG flag
* @method JsonConverter withHexAmp() adds the JSON_HEX_AMP flag
* @method JsonConverter withoutHexAmp() removes the JSON_HEX_AMP flag
* @method JsonConverter withHexApos() adds the JSON_HEX_APOS flag
* @method JsonConverter withoutHexApos() removes the JSON_HEX_APOS flag
* @method JsonConverter withHexQuot() adds the JSON_HEX_QUOT flag
* @method JsonConverter withoutHexQuot() removes the JSON_HEX_QUOT flag
* @method JsonConverter withForceObject() adds the JSON_FORCE_OBJECT flag
* @method JsonConverter withoutForceObject() removes the JSON_FORCE_OBJECT flag
* @method JsonConverter withNumericCheck() adds the JSON_NUMERIC_CHECK flag
* @method JsonConverter withoutNumericCheck() removes the JSON_NUMERIC_CHECK flag
* @method JsonConverter withUnescapedSlashes() adds the JSON_UNESCAPED_SLASHES flag
* @method JsonConverter withoutUnescapedSlashes() removes the JSON_UNESCAPED_SLASHES flag
* @method JsonConverter withPrettyPrint() adds the JSON_PRETTY_PRINT flag
* @method JsonConverter withoutPrettyPrint() removes the JSON_PRETTY_PRINT flag
* @method JsonConverter withUnescapedUnicode() adds the JSON_UNESCAPED_UNICODE flag
* @method JsonConverter withoutUnescapedUnicode() removes the JSON_UNESCAPED_UNICODE flag
* @method JsonConverter withPartialOutputOnError() adds the JSON_PARTIAL_OUTPUT_ON_ERROR flag
* @method JsonConverter withoutPartialOutputOnError() removes the JSON_PARTIAL_OUTPUT_ON_ERROR flag
* @method JsonConverter withPreserveZeroFraction() adds the JSON_PRESERVE_ZERO_FRACTION flag
* @method JsonConverter withoutPreserveZeroFraction() removes the JSON_PRESERVE_ZERO_FRACTION flag
* @method JsonConverter withUnescapedLineTerminators() adds the JSON_UNESCAPED_LINE_TERMINATORS flag
* @method JsonConverter withoutUnescapedLineTerminators() removes the JSON_UNESCAPED_LINE_TERMINATORS flag
* @method JsonConverter withObjectAsArray() adds the JSON_OBJECT_AS_ARRAY flag
* @method JsonConverter withoutObjectAsArray() removes the JSON_OBJECT_AS_ARRAY flag
* @method JsonConverter withBigintAsString() adds the JSON_BIGINT_AS_STRING flag
* @method JsonConverter withoutBigintAsString() removes the JSON_BIGINT_AS_STRING flag
* @method JsonConverter withInvalidUtf8Ignore() adds the JSON_INVALID_UTF8_IGNORE flag
* @method JsonConverter withoutInvalidUtf8Ignore() removes the JSON_INVALID_UTF8_IGNORE flag
* @method JsonConverter withInvalidUtf8Substitute() adds the JSON_INVALID_UTF8_SUBSTITUTE flag
* @method JsonConverter withoutInvalidUtf8Substitute() removes the JSON_INVALID_UTF8_SUBSTITUTE flag
* @method JsonConverter withThrowOnError() adds the JSON_THROW_ON_ERROR flag
* @method JsonConverter withoutThrowOnError() removes the JSON_THROW_ON_ERROR flag
*/
final class JsonConverter
{
Expand All @@ -45,6 +101,30 @@ final class JsonConverter
/** @var Closure(string, array-key): string */
private readonly Closure $internalFormatter;

/**
* @return array<string, int>
*/
private static function methodToFlag(): array
{
static $methods;
if (null === $methods) {
$flagNames = array_keys(
array_filter(
get_defined_constants(true)['json'],

Check failure on line 113 in src/JsonConverter.php

View workflow job for this annotation

GitHub Actions / PHP on 8.3 - prefer-stable -

Parameter #1 $array of function array_filter expects array, mixed given.
fn (string $key) => str_starts_with($key, 'JSON_') && !str_starts_with($key, 'JSON_ERROR_'),

Check failure on line 114 in src/JsonConverter.php

View workflow job for this annotation

GitHub Actions / PHP on 8.3 - prefer-stable -

Parameter #2 $callback of function array_filter expects (callable(mixed): bool)|null, Closure(string): bool given.
ARRAY_FILTER_USE_KEY
)
);

$methods = [];
foreach ($flagNames as $name) {
$methods[lcfirst(str_replace('_', '', ucwords(strtolower(substr($name, 5)), '_')))] = constant($name);
}
}

return $methods;
}

public static function create(): self
{
return new self(
Expand Down Expand Up @@ -79,6 +159,18 @@ private function __construct(
$this->internalFormatter = $this->setInternalFormatter();
}

/**
* @throws BadMethodCallException
*/
public function __call(string $name, array $arguments): self
{
return match (true) {
str_starts_with($name, 'without') => $this->removeFlags(self::methodToFlag()[lcfirst(substr($name, 7))] ?? throw new BadMethodCallException('The method "'.$name.'" does not exist.')),
str_starts_with($name, 'with') => $this->addFlags(self::methodToFlag()[lcfirst(substr($name, 4))] ?? throw new BadMethodCallException('The method "'.$name.'" does not exist.')),
default => throw new BadMethodCallException('The method "'.$name.'" does not exist.'),
};
}

/**
* Adds a list of JSON flags.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/JsonConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use PHPUnit\Framework\TestCase;

use const JSON_FORCE_OBJECT;
use const JSON_HEX_QUOT;
use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_SLASHES;

Expand Down Expand Up @@ -112,4 +113,22 @@ public function it_can_manipulate_the_record_prior_to_json_encode(): void

self::assertSame('[{"foo":"BAR"}]', $converter->encode([['foo' => 'bar']]));
}

#[Test]
public function it_can_use_syntactic_sugar_methods_to_set_json_flags(): void
{
$usingJsonFlags = JsonConverter::create()
->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT)
->removeFlags(JSON_HEX_QUOT)
->depth(24);

$usingMethodFlags = JsonConverter::create()
->withPrettyPrint()
->withUnescapedSlashes()
->withForceObject()
->withoutHexQuot()
->depth(24);

self::assertEquals($usingJsonFlags, $usingMethodFlags);
}
}

0 comments on commit b05034e

Please sign in to comment.