なぜか、うんざりするほど詰まった話(解決付)。 PHP → javascript → PHP の多次元配列jsonデータの受け渡し

# CSVをアップロードして、データベースの情報を追記して表示し、DLできるようにする機能を作っていたのですが、追記する情報の中に、シングルクォート(')を含んでいる文字列（例　I'm Fine！）があるとDLできずにとてもとても詰まったので、ここにメモしておきます。

<img width="956" alt="スクリーンショット 2020-05-21 20.31.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463891/26452d14-bd78-cb7d-879c-c6149d649212.png">


## 環境
PC: MacBook Air
Browser: Chrome
Version: Laravel Framework 5.8.16

## 参考文献
[[PHP]preg_replaceで複数の文字列置換](https://ktyr.net/?p=999)
[【PHP】複数文字列の置換(str_replace)](https://qiita.com/kazu56/items/35c10fdd79393686b26d)
[【PHP】JSONがnullになるケース](https://mimirswell.ggnet.co.jp/blog-247)
[Javascript|JSONでの、レコードの表現方法（配列、連想配列）→{}と[]の違い](https://blog.goo.ne.jp/xmldtp/e/20ee95b2f8c620be2b3a211cc8dd7358)
[【JavaScript】JSONのparseとstringifyメソッドの使い方](https://www.task-notes.com/entry/20160719/1468858991)
[PHP: json_decode を使って Unicode エスケープシーケンスを UTF-8 の文字列に変換する](https://blog.sarabande.jp/post/53806452750)
[PHPでjson_encodeとjson_decodeを使ってみる](http://cly7796.net/wp/php/dealing-with-json-in-php/)
[htmlspecialchars](https://www.php.net/manual/ja/function.htmlspecialchars.php)
[array_walk_recursiveでクロージャを使う](https://polidog.jp/2012/01/13/array_walk_recursive/)
[JSON でのエスケープ処理 (JSONの値に""", "\" を含める場合の処理)](https://www.ipentec.com/document/json-character-escape)
[PHP エスケープシーケンスのサンプル](https://itsakura.com/php-escapesequence)
[JSONデータに改行コードを入れる方法](https://www.sria.co.jp/blog/2017/09/4108/)
[JSONについて調べてみた](https://qiita.com/chihiro/items/dd1912c1406dbfe16b72)
[How to deal with backslashes in json strings php](https://stackoverflow.com/questions/32056940/how-to-deal-with-backslashes-in-json-strings-php)
[json_encode() escaping forward slashes](https://stackoverflow.com/questions/10210338/json-encode-escaping-forward-slashes)
[Posting JSON from hidden form field](https://stackoverflow.com/questions/9258729/posting-json-from-hidden-form-field)
[how to use JSON.stringify and json_decode() properly](https://stackoverflow.com/questions/15986235/how-to-use-json-stringify-and-json-decode-properly)
[JSONのエスケープ](https://blog.ohgaki.net/json-escape#PHPjson_encode)
[PHPとJavaScriptでHTMLエンティティを扱う時のおさらい](https://qiita.com/ka215/items/ace36f55c3ad1297de81)
[json_encode](https://www.php.net/manual/ja/function.json-encode.php)
[json_encode can not escape single quotation marks?](https://stackoverflow.com/questions/44903824/json-encode-can-not-escape-single-quotation-marks)
[How should I go about adding slashes to only single quotes and ignoring double quotes?](https://stackoverflow.com/questions/2754404/how-should-i-go-about-adding-slashes-to-only-single-quotes-and-ignoring-double-q)
[【PHP入門】正規表現で置換する方法](https://www.sejuku.net/blog/22175)
[JSON.stringifyは、文字列化されるたびに二重引用符をエスケープします](https://www.it-swarm.dev/ja/javascript/jsonstringify%E3%81%AF%E3%80%81%E6%96%87%E5%AD%97%E5%88%97%E5%8C%96%E3%81%95%E3%82%8C%E3%82%8B%E3%81%9F%E3%81%B3%E3%81%AB%E4%BA%8C%E9%87%8D%E5%BC%95%E7%94%A8%E7%AC%A6%E3%82%92%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E3%81%97%E3%81%BE%E3%81%99/1073719002/)
[How do I json_decode string with special chars (“ \\ ” )](https://stackoverflow.com/questions/39538490/how-do-i-json-decode-string-with-special-chars)
[htmlspecialchars_decode](https://www.php.net/manual/ja/function.htmlspecialchars-decode.php)


---

 (:baby_tone2:以下、現象説明から解決まで:baby_tone2:)

### 現象説明

##### Laravelで作っている管理画面で、IDが載っているCSVを管理画面上でアップロードしたら、ajaxで、PHP側でそのIDをもとに紐づく情報をデータベースから取ってきて、CSVのデータにくっつけて、テーブルに表示し、CSVとしてDLもできるようにする機能を作ったのですが、

##### 紐づく情報の中に、「'」(シングルクオート)が入っている場合に、JavaScriptのエラーが出てDLできない事象が起こりました。そして、Javascript側のエラーを解消すると、今度はPHP側でエラーが起こってしまう、という事態になりました。

まず、Javascript側でエラーになってしまっていた原因は、
そもそもの仕組みとして、DLボタンが押されたら、

`scriptでformを生成してhiddenのvalue値にjson形式でデータを入れてsubmitさせる`

という形だったのですが、
そのvalue値が、「'」(シングルクオート)があることにより、途中で途切れてしまう為、第一弾のエラーが起こってしまっておりました。

```javascript:エラーが起きたコード
$(document).ready(function(){
    //CSVダウンロード
    function download_csv(){

    // csvデータを作成
    var csvData = '<?= json_encode($csv_data) ?>';

    // ダウンロードを実行
    $('<form target="_blank" action="【PHP側のURL】" method="POST">' +
        '<?= csrf_field() ?>' +
        '<input type="hidden" name="csv_data" value=\'' + JSON.stringify(csvData) + '\'>' +
        '</form>').appendTo(document.body).submit();
    }
    $("#dl-btn").on('click', function(e) {
        download_csv();
    });
});

```

そこで、json_encodeを見直し、「'」(シングルクオート)をエスケープしたりすることで、javascriptのエラーを起こさずに、PHP側に遷移できるようになったのですが、

しかしながら、今度はエラー第二弾。
PHP側で、POSTで送られてきた上記CSVのデータをjson_decode()した後に、foreachを回していたのですが、
そのforeachの部分で、エラーが起きてしまいました。

デバッグしてみると、json_decode()した後のデータが入っているはずの$csv_datがNULLとなっており、
NULLなのにforeachを回そうとしてエラーになっておりました。

```php5:エラーが起きたコード

    public function dlCsvConversion(Request $request)
    {
        assert(isset($_POST['csv_array']));
        $csv_dat = json_decode(json_decode($_POST['csv_array'], true),true);
        $csv = '';
        foreach ($csv_dat as $value){
            foreach ($value as $k => $val){
                if ($k === count($value)-1) {
                    $csv.=$val;
                } else {
                    $csv.=$val."\t";
                }
            }
            $csv.="\r\n";
        }
        header('Content-Encoding: UTF-16LE');
        header('Content-type: text/csv; charset=UTF-16LE');
        header('Content-Disposition: attachment; filename="convertedCsv-'.date('Ymd').'.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo chr(255) . chr(254) . iconv("UTF-8", "UTF-16LE", $csv);
        exit;
    }
```

だけど、POST値はきちんと送られてきております。
なのに、json_decode()の結果がNULL。

検索してみると、json_decode()に失敗するとNULLとなってしまうことがわかりました。
そこで、色々と改善策を検索して試してみたのですが、
今回、json配列が[]（鍵カッコ）で括られた、行列のみ多次元配列のデータだった為、key:valueのような形の{}で囲われたjson配列の記事はたくさんあったのですが、その違いのためかうまくいかない手法ばかりでした。

### 解決

ただ、試行錯誤しているうちに、

POST値がjson_encode()したはずなのに、なぜか「'」(シングルクオート)などがdecode済みの形で入ってきてしまっていたのを

javascript側を

`json_encode(json_encode($csv_data, JSON_HEX_APOS))`

上記のように最初に「'」(シングルクオート)だけ指定してjson_encodeし、さらに全体をjson_encodeし、JSON.stringify()は使わないようにすることで、

PHP側にencodeしたままの形で渡せるようになり、
さらに、今までは

`""[[],[]]""`

上記のように、javascript側で２回json_encode（）していることで、「"」（ダブルクオーテーション）が入れ子になってしまっているのをjson_encode()を入れ子にして２回実施することで、解凍していたのですが、
それをpreg_replace（）で最初と最後の「"」（ダブルクオーテーション）を取り除き、json_encode()を１回だけにしたところ、json_decode()がSyntax errorでNULLになってしまうのを無事解消することができました！！

```javascript:修正後のコード（javascript側）
$(document).ready(function(){
    //CSVダウンロード
    function download_csv(){

    // csvデータを作成
    var csvData = '<?= json_encode(json_encode($csv_array, JSON_HEX_APOS)) ?>';

    // ダウンロードを実行
    $('<form target="_blank" action="【PHP側のURL】" method="POST">' +
        '<?= csrf_field() ?>' +
        '<input type="hidden" name="csv_array" value=\'' + csvData + '\'>' +
        '</form>').appendTo(document.body).submit();
    }
    $("#dl-btn").on('click', function(e) {
        download_csv();
    });
});
```

```php5:修正後のコード（PHP側）

    public function dlCsvConversion(Request $request)
    {
        assert(isset($_POST['csv_array']));
        $search = array('/^"/','/"$/');
        $replace = array('','');
        $csv_data = json_decode(preg_replace($search, $replace, $_POST['csv_data']),true);
        $csv = '';
        foreach ($csv_data as $value){
            foreach ($value as $k => $val){
                if ($k === count($value)-1) {
                    $csv.=$val;
                } else {
                    $csv.=$val."\t";
                }
            }
            $csv.="\r\n";
        }
        header('Content-Encoding: UTF-16LE');
        header('Content-type: text/csv; charset=UTF-16LE');
        header('Content-Disposition: attachment; filename="paygentCsvConverted-'.date('Ymd').'.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo chr(255) . chr(254) . iconv("UTF-8", "UTF-16LE", $csv);
        exit;
    }
```

---

（:alien:振り返り:alien:）

そもそものコードもちょっとおかしかったのですが、どん詰まり状態から開放されてとても嬉しい気分です。
直したコード的にはあっけなかったのですが、エンコードのデバッグはとてもわかりづらくて辛かった。。
Unicode文字列とかまで試したりして、結局は使いませんでしたが、勉強にはなりました！
POST値がencode済だったのは多分JSON.stringify()していたせいですかね、、
直せたけど、曖昧なままな部分もある。。でもとりあえず、よしとしておきます！

以上！