json — json_encode объекта-генератора (используя yield)

У меня очень большой массив в PHP (5.6), сгенерированный динамически, который я хочу преобразовать в JSON. Проблема в том, что массив слишком большой, чтобы он не помещался в памяти — я получаю фатальную ошибку, когда пытаюсь его обработать (исчерпана память). Итак, я понял, что при использовании генераторов проблема с памятью исчезнет.

Это код, который я пробовал до сих пор (этот сокращенный пример не приводит к ошибке памяти):

<?php
function arrayGenerator()// new way using generators
{
for ($i = 0; $i < 100; $i++) {
yield $i;
}
}

function getArray()// old way, generating and returning the full array
{
$array = [];
for ($i = 0; $i < 100; $i++) {
$array[] = $i;
}
return $array;
}

$object = [
'id' => 'foo',
'type' => 'blah',
'data' => getArray(),
'gen'  => arrayGenerator(),
];

echo json_encode($object);

Но, похоже, PHP не JSON-кодирует значения из генератора. Это вывод, который я получаю из предыдущего скрипта:

{
"id": "foo",
"type": "blah",
"data": [// old way - OK
0,
1,
2,
3,
//...
],
"gen": {}// using generator - empty object!
}

Возможно ли даже JSON-кодирование массива, созданного генератором, без генерации полной последовательности, прежде чем я вызову json_encode?

11

Решение

К сожалению, json_encode не может сгенерировать результат из функции генератора. С помощью iterator_to_array все равно будет пытаться создать весь массив, что все равно вызовет проблемы с памятью.

Вам нужно будет создать свою функцию, которая будет генерировать строку json из функции генератора. Вот пример того, как это может выглядеть:

function json_encode_generator(callable $generator) {
$result = '[';

foreach ($generator as $value) {
$result .= json_encode($value) . ',';
}

return trim($result, ',') . ']';
}

Вместо того, чтобы кодировать весь массив одновременно, он кодирует только один объект за раз и объединяет результаты в одну строку.

Приведенный выше пример заботится только о кодировании массива, но его можно легко расширить для рекурсивного кодирования целых объектов.

Если созданная строка все еще слишком велика, чтобы уместиться в памяти, то единственная оставшаяся опция — напрямую использовать выходной поток. Вот как это может выглядеть:

function json_encode_generator(callable $generator, $outputStream) {
fwrite($outputStream, '[');

foreach ($generator as $key => $value) {
if ($key != 0) {
fwrite($outputStream, ',');
}

fwrite($outputStream, json_encode($value));
}

fwrite($outputStream, ']');
}

Как видите, единственная разница в том, что мы сейчас используем fwrite для записи в переданный поток вместо конкатенации строк, мы также должны по-другому позаботиться о конечной запятой.

6

Другие решения

Что такое функция генератора?

Функция генератора — фактически более компактный и эффективный способ написать Итератор. Это позволяет вам определить функцию, которая будет рассчитать и вернуть ценности в то время как ты зацикливаясь на этом:

Также согласно документу от http://php.net/manual/en/language.generators.overview.php

Генераторы предоставляют простой способ реализации простых итераторов без дополнительных затрат и сложности реализации класса, реализующего интерфейс итератора.

Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти или значительному времени обработки для генерации. Вместо этого вы можете написать функцию генератора, которая аналогична обычной функции, за исключением того, что вместо однократного возврата генератор может выдавать столько раз, сколько ему нужно, чтобы предоставить значения для повторения.

Что такое yield?

yield ключевое слово возвращает данные из функции генератора:

Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield очень похож на оператор return, за исключением того, что вместо остановки выполнения функции и возврата yield вместо этого предоставляет значение для кода, зацикливающегося над генератором, и приостанавливает выполнение функции генератора.

Таким образом, в вашем случае, чтобы генерировать ожидаемый результат, вам нужно повторить вывод arrayGenerator() функция с помощью foreach петля или iterator перед обработкой его в json (как предложено @apokryfos)

1

По вопросам рекламы [email protected]