Рекурсивно декодировать строку JSON до тех пор, пока декодированная строка не станет допустимой JSON

У меня есть одноразовая строка в кодировке URL:

$encodedJson = "%5B%7B%0A%09%22base%22%3A%20%7B%0A%09%09%22url%22%3A%20%22abc.com%22%2C%0A%09%09%22referrer%22%3A%20%22xyz.com%22%0A%09%7D%0A%7D%2C%20%7B%0A%09%22client%22%3A%20%7B%0A%09%09%22Pixel%22%3A%20false%2C%0A%09%09%22screen%22%3A%20%221680x1050%22%0A%09%7D%0A%7D%5D"

Если я использую следующие функции, у меня есть декодированный JSON, который представляет собой массив:

$decodedJsonArray = json_decode(rawurldecode($encodedJson), true);

Затем print_r($decodedJsonArray); дает мне желаемый результат:

Array
(
    [0] => Array
        (
            [base] => Array
                (
                    [url] => abc.com
                    [referrer] => xyz.com
                )

        )

    [1] => Array
        (
            [client] => Array
                (
                    [Pixel] => 
                    [screen] => 1680x1050
                )

        )

)

Теперь предположим, что у меня есть строка с многократным кодированием URL:

$encodedJson = "%25255B%25257B%25250A%252509%252522base%252522%25253A%252520%25257B%25250A%252509%252509%252522url%252522%25253A%252520%252522abc.com%252522%25252C%25250A%252509%252509%252522referrer%252522%25253A%252520%252522xyz.com%252522%25250A%252509%25257D%25250A%25257D%25252C%252520%25257B%25250A%252509%252522client%252522%25253A%252520%25257B%25250A%252509%252509%252522Pixel%252522%25253A%252520false%25252C%25250A%252509%252509%252522screen%252522%25253A%252520%2525221680x1050%252522%25250A%252509%25257D%25250A%25257D%25255D"

Эта строка представляет собой трехкратный URL-код. Теперь я хочу получить тот же массив JSON, что и раньше. Я пытаюсь написать функцию, подобную следующей:

function recursiveJsonDecode($encodedJson) {
    if (isJson($encodedJson)) {
        return $encodedJson;
    } else {
        $decodedJsonArray = json_decode(rawurldecode($encodedJson), true);
        return $decodedJsonArray;
    }
}

Но это не работает. Любая помощь приветствуется.


person sumion    schedule 29.11.2017    source источник
comment
Что именно не работает во втором примере? покажите нам результат, который вы получите.   -  person Filnor    schedule 29.11.2017
comment
Я не знал, что в PHP есть функция с именем isJson   -  person Andreas    schedule 29.11.2017
comment
Очень важной особенностью рекурсивной функции является то, что она вызывает сама себя.   -  person Don't Panic    schedule 29.11.2017
comment
@DontPanic функция, которая этого не делает, называется глухонемой рекурсивной функцией   -  person Andreas    schedule 29.11.2017
comment
Это просто теоретическое упражнение, я полагаю? Потому что на самом деле вам не следует иметь дело с данными, которые были закодированы более одного раза для начала ... это скорее будет причиной отклонить данные IMHO и сообщить тому, кто их отправляет вам, чтобы привести их вещи в порядок...   -  person CBroe    schedule 29.11.2017
comment
@Andreas Андреас, у меня есть функция isJson в том же файле, которая работает. function isJson($string) { json_decode($string); return (json_last_error() == JSON_ERROR_NONE); }   -  person sumion    schedule 29.11.2017
comment
@CBroe да, это просто предположение. Обычно он кодируется только один раз. Но в случае, если он закодирован несколько раз...   -  person sumion    schedule 29.11.2017


Ответы (4)


Доставка и почтовые принадлежности | USPS.com — Почтовый магазин

Заказывать товары для доставки — это мило, потому что это единственный раз, когда вы можете получить коробку, полную только коробок!

Когда вы получаете свои коробки по почте, что вы с ними делаете? Я снимаю только крайнюю упаковку и ставлю коробки на полку; может быть, я использую их, чтобы отправить вещи позже. Кто-то, кто пишет рекурсивный декодер JSON, может сделать что-то по-другому — он может попытаться открыть все эти ящики и расстроиться, обнаружив, что ничего не получил!

"Я открыл каждую коробку, но так и не нашел содержимое своего заказа!" сокрушается рекурсивный декодер JSON.


Не расшифровывайте его только потому, что можете

Невозможно определить, закодирована ли строка в формате JSON или нет. Из-за этого потребитель не должен решать, проводить синтаксический анализ или нет.

Возьмем, к примеру, строку JSON, "5" — это закодированная строка из '5' в одинарном коде?

json_encode("5");
// => '"5"'

или это целое число 5 с двойным кодированием?

json_encode(json_encode(5));
// => '"5"'

Если вы смотрите только на результат, закодированный в формате JSON, невозможно сказать, но 5 (int) и "5" (string) так же отличаются, как [5] или {value: 5} — это совершенно разные типы — потребитель JSON должен знать, сколько раз значение было закодировано. Это не сложно, так как вам следует избегать двойного кодирования в первую очередь.


Когда мы декодируем JSON, мы делаем это только один раз

json_decode('"5"');
// => "5"

Ваша рекурсивная функция эффективно сделает это

json_decode(json_decode('"5"'));
// => 5

Только один из них является правильным ответом — вот почему вы видите все функции isJson, построенные вокруг проверки ошибок операции декодирования — люди обманывают себя, думая, что только потому, что вы можете декодировать строку, она была JSON в первую очередь.

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

Идея о том, что вы можете каким-то образом определить, когда остановить декодирование, не работает с самого начала. В этом примере посмотрите, что происходит, когда у меня есть простая отправка формы и рекурсивный декодер JSON, используемый для обработки отправки. ..

Если я заполню форму с моим именем "[]", и теперь вы используете рекурсивный декодер JSON для отправленных данных формы, вы получите

$formData == [ "name" => [] ] // name is an array, wups!

В то время как нерекурсивный декодер JSON сохранит имя в виде строки

$formData == [ "name" => "[]" ] // name is a string, as the user typed

Тот факт, что вы можете разобрать его, не означает, что вы должны


После того, как строка была закодирована двойным или тройным кодированием (URL-кодированием, JSON-кодированием или каким-то еще-кодированием), единственный способ обратить ее — это декодировать ее точно такое же количество раз.

person Mulan    schedule 29.11.2017
comment
Тогда почему все его образцы кода написаны на php, а в его вопросе есть тег php? - person Ethan; 29.11.2017
comment
Вопрос довольно расплывчатый. Я понял, что речь идет о кодировании URL, а не конкретно о JSON. - person apokryfos; 29.11.2017
comment
Вопросы не являются расплывчатыми imo — человек как рекурсивно декодировать JSON и показывает функцию с их попыткой сделать это - person Mulan; 29.11.2017
comment
@naomik Я думаю, что первая строка вашего ответа неверна, см. этот пост SO, чтобы узнать, как обнаружить данные JSON: stackoverflow.com/q/ 6041741/3088508 - person Ethan; 29.11.2017
comment
Дэвид, только потому, что строка может быть декодирована в формате JSON, не означает, что это JSON - person Mulan; 29.11.2017
comment
@David, также обратите внимание, что каждое решение зависит от проверки ошибок, что не является надежным сигналом того, следует ли анализировать строку или нет - прочитайте также комментарии других пользователей - неудивительно, что также нет принятого ответа - я готов для вас, чтобы удалить свой голос против, когда вы можете отказаться от своей гордости - person Mulan; 29.11.2017

Вы можете рассматривать декодирование URL как операцию с фиксированной точкой:

function fixedPointDecode($string) {
     $decoded = urldecode($string); 
     while ($decoded != $string) {
         $string = $decoded;
         $decoded = urldecode($string);    
     }
     return $decoded;
}

Идея состоит в том, что если результат urldecode не изменяет исходную строку, то она полностью декодирована.

Затем вы можете сделать:

 json_decode(fixedPointDecode($string));

Примечание. Я не нашел никаких указаний на то, что есть какие-либо строки в кодировке URL, которые не сходятся к фиксированной точке, но мне любопытно, есть ли у кого-нибудь еще.

person apokryfos    schedule 29.11.2017
comment
Единственная проблема заключается в том, что иногда вы сталкиваетесь с допустимым значением, которое также кажется закодированным. Скажем, у меня есть идентификатор устройства frt%235 — это фактический идентификатор устройства — если я позволю программе решить, когда прекратить декодирование, она изменит его на frt#5, что в данном случае недопустимо. - person Mulan; 29.11.2017
comment
Если вы измените значение while на while (json_decode($decoded) == null), это может сработать, но в этом случае функция вызовет бесконечный цикл, если во входных данных нет скрытой допустимой строки json. Я рекомендую вам решить проблему в ее источнике, убедившись, что строка имеет значение urlencoded только один раз. - person apokryfos; 29.11.2017
comment
но в том-то и дело, что потребитель не может контролировать содержимое данных — он не может полагаться на проверки нулей/ошибок как на сигнал о том, следует ли анализировать строку. - person Mulan; 29.11.2017
comment
Что ж, это отчасти правда, но это также то, что вы получаете, выбирая глючных провайдеров. Получение строки с urlencoded несколько раз должно заслужить отчет об ошибке поставщику, и поставщик не должен заявлять, что он работает должным образом. Ни в каком мире двойное кодирование всей строки не должно иметь никакого смысла. - person apokryfos; 29.11.2017
comment
я полностью согласен - так что в случае с OP, если он дважды закодирован (по какой-то глупой причине), просто двойное декодирование - если он тройное кодирование по еще более глупой причине, просто тройное декодирование - рекурсия и обнаружение для точка остановки не требуется или возможна - person Mulan; 29.11.2017
comment
@naomik Если вы знаете, сколько раз это кодируется заранее, то это определенно лучшая стратегия, чем операция с фиксированной точкой. - person apokryfos; 29.11.2017

json_decode вернет null, если это недопустимый JSON, как указано здесь:

NULL возвращается, если json не может быть декодирован или если закодированные данные глубже предела рекурсии.

Так что просто проверьте это:

while(($decodedJsonArray = json_decode($encodedJson, true)) === null) {
    $encodedJson = rawurldecode($encodedJson);
}

print_r($decodedJsonArray);

Чтобы использовать функцию isJson:

while(!isJson($encodedJson)) {
    $encodedJson = rawurldecode($encodedJson);
}
$decodedJsonArray = json_decode($encodedJson, true);

print_r($decodedJsonArray);
person AbraCadaver    schedule 29.11.2017
comment
Вау, это намного чище, чем моя рекурсивная функция. Проголосуйте! - person Ethan; 29.11.2017
comment
Чтобы пойти со случайным отрицательным голосом :( - person AbraCadaver; 29.11.2017
comment
Привет, как я прокомментировал ответ Дэвида, ваша функция также работает, если у меня есть строка типа $encodedJson = "%25etcetc";, но она не работает, когда я анализирую строку из файла, то есть $encodedJson = file_get_contents("test.txt"); или $encodedJson = file_get_contents("test.json");. Есть идеи, почему? - person sumion; 29.11.2017
comment
AbraCadavar, вы только что заказали 1 упаковку Priority Mail Коробка для обуви от USPS.com — сколько коробок вы откроете, когда получите свой заказ? - person Mulan; 29.11.2017

Вызов rawurldecode(rawurldecode(rawurldecode($encodedJson))) показывает, что ваша строка на самом деле rawurldecoded 3 раза, а не json_encoded 3 раза, поэтому я сделал рекурсивную функцию rawurldecode на каждой итерации, пока json_decode не сработало:

$encodedJson = "%25255B%25257B%25250A%252509%252522base%252522%25253A%252520%25257B%25250A%252509%252509%252522url%252522%25253A%252520%252522abc.com%252522%25252C%25250A%252509%252509%252522referrer%252522%25253A%252520%252522xyz.com%252522%25250A%252509%25257D%25250A%25257D%25252C%252520%25257B%25250A%252509%252522client%252522%25253A%252520%25257B%25250A%252509%252509%252522Pixel%252522%25253A%252520false%25252C%25250A%252509%252509%252522screen%252522%25253A%252520%2525221680x1050%252522%25250A%252509%25257D%25250A%25257D%25255D";

function recursiveJsonDecode ($inJson) {
    $outputArr = json_decode($inJson);
    if (json_last_error() == JSON_ERROR_NONE) {
        return $outputArr;
    } else {
        return recursiveJsonDecode(rawurldecode($inJson));
    }
}

print_r(recursiveJsonDecode($encodedJson));

демонстрация eval.in

person Ethan    schedule 29.11.2017
comment
это не может работать при применении к любым общим данным - person Mulan; 29.11.2017
comment
Судя по входным данным ОП, я бы сказал, что это сработает, если оно будет испорчено так же, как его входной массив (сначала json_encoded, затем rawurlencoded). См. здесь: eval.in/910031 - person Ethan; 29.11.2017
comment
Привет, ваша функция работает, если у меня есть строка типа $encodedJson = "%25etcetc"; Но она не работает, когда я анализирую строку из файла, то есть $encodedJson = file_get_contents("test.txt"); или $encodedJson = file_get_contents("test.json"); . Есть идеи, почему? - person sumion; 29.11.2017
comment
@IqbalNazir У меня работает, когда я получаю $encodedJson из массива, но поскольку я на самом деле не знаю, что находится в вашем файле test.txt, я просто предполагаю, что это то, что вы опубликовали в своем ответе. См. этот файл eval.in: eval.in/910041 Пожалуйста, разместите содержимое своего test.txt файла на каком-либо сервисе, например www. pastebin.com, если мое предположение неверно. - person Ethan; 29.11.2017
comment
Спасибо чувак. Это действительно работает. У меня был символ в моем текстовом файле. После их удаления все работает. - person sumion; 29.11.2017
comment
@IqbalNazir, вы думаете, что он работает сейчас только потому, что вы не видите, как легко он ломается, как только данные изменяются - person Mulan; 29.11.2017
comment
Дэвид, вы только что заказали 1 упаковку Priority Mail. Коробка для обуви от USPS.com — сколько коробок вы откроете, когда получите свой заказ? - person Mulan; 29.11.2017