ラボ > Javascript関連:ajax、form

iOS(アプリ)+ajaxでDataCloneError

Webやアンドロイド(アプリ)では問題なかったけどiOS(アプリ)のみで「DataCloneError: The object could not be cloned.」が出てくる

作成日:2020-05-18, 更新日:2020-05-21

元ソースと対応

・HTML側にformタグは無い(※Javascriptで送りたいデータを準備する)

対応後
// 送りたいデータを都度、作成
var fd = new FormData();
fd.append('hoge01', xx1);
fd.append('hoge02', xx2);
fd.append('hoge03', xx3);

$.ajax({
   url: xxxxxxxxxxxx,
   data: fd,
   type: 'POST',
   dataType: 'json',
   contentType: false,
   processData: false,
}).done(function(data) {
   ~以下、略~
// 送りたいデータを都度、作成
$('#ID_xxxx').remove();
var _ipt = {
   hoge01: xx1,
   hoge02: xx2,
   hoge03: xx3
};
var _form = document.createElement("form");
for (var i in _ipt) {
   var _tmpInput = document.createElement("input");
   _tmpInput.setAttribute("name", i);
   _tmpInput.setAttribute("type", "text");
   _tmpInput.setAttribute("value", _ipt[i]);
   _form.appendChild(_tmpInput);
}
_form.id = "ID_xxxx";
_form.style = "overflow:hidden;width:0;height:0;";
document.body.appendChild(_form);

var fd = $('#ID_xxxx').serialize();

$.ajax({
   url: xxxxxxxxxxxx,
   data: fd,
   type: 'POST',
   dataType: 'json',
   contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
   processData: false,
}).done(function(data) {
   ~以下、略~

※serialize()を使うためにわざわざformタグを作っている

・元データは、「FormData()」を「contentType:false」で送る
・変更後は、「serialize()」したデータを「contentType:'application/x-www-form-urlencoded; charset=UTF-8'」で送る

めも

  • 「FormData()」が使えない(IE8、IE9以外にも最新のiOSアプリだと使えないってコトかな?)
  • 「serialize()」+「POST」で送るなら「contentType:'application/x-www-form-urlencoded; charset=UTF-8'」にする必要があるっぽい
    ※「FormData()」のときは「contentType:false」

serialize()

「serialize()」はformタグの中身を「hoge01=xx1&hoge02=xx2&hoge03=xx3」という文字列に変換してくれる関数。
※対象は、name属性があるヤツだけ

エスケープ処理をしてやるならわざわざ「serialize()」を使わなくてもいいはず。

経緯

・もともとWEBがあって、ajaxを使ってゴニョゴニョしていた
・そのWEBをアプリで表示するようにした(他人が作業:詳細不明)
・アプリのバージョンアップに伴いiOS(アプリ)でエラーになる
→「修正が必要です」と言われた

状況

・PCでWEBを確認→問題なし
・アンドロイドからWEBを確認→問題なし
・アンドロイド(アプリ)で確認→問題なし
・iOSからWEBを確認→問題なし
iOS(アプリ)で確認→エラー

サーバ側(PHP)

サーバ側(PHP)・・・エラー以前にそもそもアクセスされていない
・・・ajax(Javascript)側でエラーがあると推測

ajax(Javascript)側の問題

iOS(アプリ)のみ値がクリア or 別の内容に書き換えられている・・・等

リクエストURLの問題

まず、ajaxで使うリクエストURLの問題
→URLがクリアされている or 書換えられている等の問題は無し

フォームデータの問題

「$(xxx).〇〇〇」みたいなトコで取得失敗している可能性
→どこもエラーになっていないっぽい

ajax自体の問題

▼こんなエラーが出ていた

DataCloneError: The object could not be cloned.

渡す値がおかしいってコトなんだろうけど・・・調べると「new FormData()」ってトコがダメと言う人たちが多いっぽい
※ただ、古い情報ばっかり・・・

調査

「new FormData()」がダメってコトで別の方法を調べた。
※色々調べながらしていたので、そもそもダメって内容も含まれる

▼元はこんな感じ

var fd = new FormData();
fd.append('hoge', 'xxx');
fd.append('foo',  'xxx');
fd.append('bar',  'xxx');
$.ajax({
   url: xxxxxxxxxxxx,
   data: fd,
   type: 'POST',
   dataType: 'json',
   contentType: false,
   processData: false,
   ~ 以下、略 ~
});

オブジェクトでそのまま送る

var fd = {
   hoge: 'xxx',
   foo:  'xxx',
   bar:  'xxx'
};

→サーバ側にデータが送られていない

オブジェクトをserialize()化

var ipt = {
   hoge: 'xxx',
   foo:  'xxx',
   bar:  'xxx'
};
var fd = ipt.serialize();

→formじゃないとダメ。

serialize()の結果を送る

serialize()の結果って「hoge01=xx1&hoge02=xx2&hoge03=xx3」のはずだから、直接、セット。

var fd = 'hoge01=xx1&hoge02=xx2&hoge03=xx3';

→サーバ側にデータが送られていない

serialize()してから送る

面倒だけど、いったんformを作成してやる。その後、作成したformをserialize()・・・という流れ

var _ipt = {
   hoge01: xx1,
   hoge02: xx2,
   hoge03: xx3
};
var _form = document.createElement("form");
for (var i in _ipt) {
   var _tmpInput = document.createElement("input");
   _tmpInput.setAttribute("name", i);
   _tmpInput.setAttribute("type", "text");
   _tmpInput.setAttribute("value", _ipt[i]);
   _form.appendChild(_tmpInput);
}
_form.id = "ID_xxxx";
_form.style = "overflow:hidden;width:0;height:0;";
document.body.appendChild(_form);

var fd = $('#ID_xxxx').serialize();

→サーバ側にデータが送られていない

GET送信

GETで送る気は無いが、GETで送れるか確認だけしておく。

$.ajax({
   ~ 略 ~
   type: 'GET',
   ~ 以下、略 ~
});

→サーバ側にデータは送られる

serialize()してJSONでおくる

var fd = $('#ID_xxxx').serialize();
$.ajax({
   ~ 略 ~
   data: JSON.stringify(fd),
   type: 'POST',
   ~ 以下、略 ~
});

→サーバ側にデータが送られていない

・・・そもそもJSONにする必要が無いから・・・

serialize()+contentTypeの設定

「contentType:false」が気になったので変更してみた。

var _ipt = {
   hoge01: xx1,
   hoge02: xx2,
   hoge03: xx3
};
var _form = document.createElement("form");
for (var i in _ipt) {
   var _tmpInput = document.createElement("input");
   _tmpInput.setAttribute("name", i);
   _tmpInput.setAttribute("type", "text");
   _tmpInput.setAttribute("value", _ipt[i]);
   _form.appendChild(_tmpInput);
}
_form.id = "ID_xxxx";
_form.style = "overflow:hidden;width:0;height:0;";
document.body.appendChild(_form);

var fd = $('#ID_xxxx').serialize();
$.ajax({
   ~ 略 ~
   contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
   ~ 以下、略 ~
});

→データが送れた

値を直で指定

試してはいないけど、下記のような感じでもいけるはず

var fd = 'hoge01=xx1&hoge02=xx2&hoge03=xx3';
$.ajax({
   ~ 略 ~
   contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
   ~ 以下、略 ~
});

この場合、エスケープ処理をしっかりしてあげる必要がある
→面倒そうなので、未調査

追記

2018年あたりの情報で「iOS+FormData()」で似たようなエラーが出ていたらしい。
この時の原因は、formに「input type=file」があって「値が空」のときに出るiOSのSafariのバグだそうだ。
→「値が空のとき、FormData()に含めない」という感じにしてやれば解決したそうだ。

私の場合は・・・・ファイルは無い。
念のために「value」が空の要素を「FormData()」に含めないようにしてみたが、エラーだった。

2020-05-21

useragentを確認してみた。

・「Win+Chrome」 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
・「iOS+ブラウザ」 - Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1
・「iOS+アプリ」 - Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) XXXXXXXXXX (iPhone7,2; iOS 12.4.6)

「XXXXXXXXXX」としているトコはカスタムのuseragent。
とりあえずSafariは関係ないっぽい。
・・・ってことは、iOSの問題 or アプリを作ったヤツが問題・・・どっちなんだろう?