Skip to content

Commit

Permalink
Improve JSONConverter::chunkSize implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Oct 12, 2024
1 parent 9789b05 commit 086a3bb
Showing 1 changed file with 60 additions and 36 deletions.
96 changes: 60 additions & 36 deletions src/JsonConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use InvalidArgumentException;
use Iterator;
use JsonException;
use JsonSerializable;
use RuntimeException;
use SplFileInfo;
use SplFileObject;
Expand Down Expand Up @@ -111,21 +112,23 @@ final class JsonConverter
private readonly bool $isForceObject;
/** @var non-empty-string */
private readonly string $indentation;
/** @var Closure(string, array-key): string */
/** @var Closure(string): string */
private readonly Closure $internalFormatter;
/** @var Closure(array<int, T>): array */
private readonly Closure $bufferFormatter;
/** @var int<1, max> */
public readonly int $chunkSize;

public static function create(): self
{
return new self(flags: 0, depth: 512, indentSize: 4, formatter: null, chunkSize: 500);
return new self(flags: 0, depth: 512, indentSize: 4, formatter: fn (mixed $value, int|string $offset) => $value, chunkSize: 500);
}

/**
* @param int<1, max> $depth
* @param int<1, max> $indentSize
*/
private function __construct(int $flags, int $depth, int $indentSize, ?Closure $formatter, int $chunkSize)
private function __construct(int $flags, int $depth, int $indentSize, Closure $formatter, int $chunkSize)
{
json_encode([], $flags & ~JSON_THROW_ON_ERROR, $depth);

Expand All @@ -136,27 +139,45 @@ private function __construct(int $flags, int $depth, int $indentSize, ?Closure $
$this->flags = $flags;
$this->depth = $depth;
$this->indentSize = $indentSize;
$this->formatter = $formatter;
$this->chunkSize = $chunkSize;
$this->indentation = str_repeat(' ', $indentSize);
$this->formatter = $formatter ?? fn (mixed $value) => $value;
$this->isPrettyPrint = ($this->flags & JSON_PRETTY_PRINT) === JSON_PRETTY_PRINT;
$this->isForceObject = ($this->flags & JSON_FORCE_OBJECT) === JSON_FORCE_OBJECT;
$this->isPrettyPrint = $this->useFlags(JSON_PRETTY_PRINT);
$this->isForceObject = $this->useFlags(JSON_FORCE_OBJECT);
$this->bufferFormatter = $this->setChunkFormatter();
$this->internalFormatter = $this->setInternalFormatter();
$this->chunkSize = $chunkSize;
}

/**
* @return Closure(string, array-key): string
* @return Closure(string): string
*/
private function setInternalFormatter(): Closure
{
$callback = match ($this->isForceObject) {
false => fn (string $json, int|string $offset): string => $json,
default => fn (string $json, int|string $offset): string => '"'.json_encode($offset).'":'.$json,
};
return $this->isPrettyPrint ? $this->prettyPrint(...) : fn (string $json): string => $json;
}

return match ($this->isPrettyPrint) {
false => $callback,
default => fn (string $json, int|string $offset): string => $this->prettyPrint($callback($json, $offset)),
/**
* @return Closure(array<int, T>): array
*/
private function setChunkFormatter(): Closure
{
return match ($this->useFlags(JSON_FORCE_OBJECT)) {
true => function (array $value): array {
$data = [];
foreach ($value as $offset => $item) {
$data[$offset] = ($this->formatter)($item, $offset);
}

return $data;
},
false => function (array $value): array {
$data = [];
foreach ($value as $offset => $item) {
$data[] = ($this->formatter)($item, $offset);
}

return $data;
},
};
}

Expand Down Expand Up @@ -239,6 +260,9 @@ public function useFlags(int ...$flags): bool
return [] !== $flags;
}

/**
* Sets the json_encode flags.
*/
private function setFlags(int $flags): self
{
return match ($flags) {
Expand Down Expand Up @@ -291,6 +315,8 @@ public function chunkSize(int $chunkSize): self
*/
public function formatter(?Closure $formatter): self
{
$formatter ??= fn (mixed $value, int|string $offset) => $value;

return new self($this->flags, $this->depth, $this->indentSize, $formatter, $this->chunkSize);
}

Expand Down Expand Up @@ -407,53 +433,51 @@ public function convert(iterable $records): Iterator
}

$offset = 0;
$incr = 0;
$buffer = [];
$current = $records->current();
$records->next();

yield $start;

$incr = 0;
$buffer = [];
while ($records->valid()) {
if ($incr === $this->chunkSize) {
yield $this->format($buffer, $offset - $incr).$separator;
yield $this->encodeBuffer($buffer).$separator;

$incr = 0;
$buffer = [];
}
$incr++;
$buffer[] = $current;

$offset++;
$incr++;
$buffer[$offset] = $current;
$current = $records->current();
$records->next();
}

if ([] !== $buffer) {
yield $this->format($buffer, $offset - $incr).$separator;
yield $this->encodeBuffer($buffer).$separator;
}

yield $this->format([$current], $offset++).$end;
yield $this->encodeBuffer([$offset => $current]).$end;
}

/**
* @param array<int, T> $buffer
*
* @throws JsonException
*/
private function format(array $value, int $offset): string
private function encodeBuffer(array $buffer): string
{
$data = [];
foreach ($value as $item) {
$data[] = ($this->formatter)($item, $offset);
++$offset;
}

$json = json_encode(
value: $data,
flags: ($this->flags & ~JSON_PRETTY_PRINT) | JSON_THROW_ON_ERROR,
depth: $this->depth
);

return ($this->internalFormatter)(substr($json, 1, -1), $offset);
return ($this->internalFormatter)(substr(
string: json_encode(
value: ($this->bufferFormatter)($buffer),
flags: ($this->flags & ~JSON_PRETTY_PRINT) | JSON_THROW_ON_ERROR,
depth: $this->depth
),
offset: 1,
length: -1
));
}

/**
Expand Down

0 comments on commit 086a3bb

Please sign in to comment.