在 AJAX 上发送嵌套的 FormData
Sending nested FormData on AJAX
我需要使用 ajax 和 FormData 发送一些数据,因为我想发送一个文件和一些其他参数。我通常发送数据的方式是这样的:
$.ajax({
type: 'POST',
url: 'some_url',
dataType: 'json',
processData:false,
contentType:false,
data:{
Lvl_1-1: 'something',
Lvl_1-2: 'something',
Lvl_1-3: {
Lvl_1-3-1: "something",
Lvl_1-3-2: "something",
Lvl_1-3-3: "something",
},
},
...
});
如果我不使用FormData(),我没有问题,但是当使用FormData()时,只有Lvl1上的数据是可以的,但是嵌套的任何东西都显示为这样的字符串
<b>array</b> <i>(size=3)</i>
'Lvl1-1' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>'Something'</font>
<i>(length=23)</i>
'Lvl1-2' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>''Something''</font> <i>(length=3)</i>
'Lvl1-3' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>'[object Object]'</font> <i>(length=17)</i>
如果我使用 FormData() 对 Lvl1-3 中的数据进行编码,而不是 [object Object]
,我会得到 [object FormData]
如何在 Lvl1-3 上获取数组而不是字符串?
注意:如果文件位于顶层 (Lvl_1),我可以使用 FormData() 毫无问题地发送文件。我没有写附加文件的代码,因为那不是问题,嵌套数据才是。我刚刚提到了该文件,因为这就是我使用 FormData() 的原因。
URL 编码的表单数据没有任何原生方式来表达复杂的数据结构。它只支持简单的键=值对。
?foo=1&bar=2
大多数表单数据解析库允许使用同名键的数据数组
?foo=1&foo=2
PHP 在该格式之上添加了自己的语法:
?foo[]=1&foo[]=2
允许关联数组中的命名键:
?foo[bar]=1&foo[baz]=2
和嵌套数组:
?foo[bar][level2a]=1&foo[bar][level2b]=2
由于 PHP 的流行,jQuery 在将 JavaScript 对象传递给 data
时采用了生成表单数据的语法。
如果您想使用 FormData
,那么 jQuery 不会为您重新处理它。
您看到的效果是因为您试图将一个对象(我猜是一个 FormData 实例,但您没有显示代码的那一部分)作为 append
的第二个参数- 需要字符串的地方。
您需要自己使用 PHP 的语法生成键名。
form_data_instance.append("Lvl_1-3[Lvl_1-3-1]", "something");
form_data_instance.append("Lvl_1-3[Lvl_1-3-2]", "something");
form_data_instance.append("Lvl_1-3[Lvl_1-3-3]", "something");
在我这边,我将嵌套参数字符串化并在另一端解析它们。
例如,如果我想通过:
{"sthing":
{"sthing":"sthing"},
{"picture":
{"legend":"great legend"},
{"file":"great_picture.jpg"}
}
}
然后我做:
// On the client side
const nestedParams = {"sthing":
{"sthing":"sthing"},
{"picture":
{"legend":"great legend"}
}
};
const pictureFile = document.querySelector('input[type="file"]')[0];
const formDataInstance = new FormData;
formDataInstance.append("nested_params": JSON.stringify(nested_params);
formDataInstance.append("file": document.querySelector('input[type="file"]')[0]);
// On the server side
params["nested_params"] = JSON.parse(params["nested_params"]);
params["nested_params"]["sthing"]["picture"]["file"] = params["file"];
为了补充 Quentin 的回答,我有一些 PHP Laravel 代码是这样的:
$tags = [];
foreach ($request->input('tags') as $tag) {
if (!is_array($tag)) {
$new_tag = Tag::generate($tag);
array_push($tags, $new_tag->id);
} else {
array_push($tags, $tag['id']);
}
}
你可以看到我从一个空数组开始,然后用 $request->input('tags')
中的值填充它。该请求输入是一个多维数组,因此遇到了这个问题中描述的问题。 仅在使用FormData和multipart/form-data
表单编码类型时表现出来。
我能够通过 Quentin 的回答加上这里的客户端 JavaScript 代码修复它:
this.example.tags.forEach((tag, i) => {
if (Object.prototype.toString.call(tag) === '[object String]') {
payload.append(`tags[${i}]`, tag);
} else {
Object.keys(tag).forEach(field => payload.append(`tags[${i}][${field}]`, tag[field]));
}
});
此代码首先检查要添加到 FormData 的标签是否为字符串。如果是,则它是一个新的、不存在的标签。然后它通过它的索引号添加它。如果标签已经存在,我只发送客户拥有的所有值。在我的例子中,服务器只关心名称和 ID(对于与该行 ID 的 SQL 关系),所以这很好,但是使用 arr[index][field]
.[=18= 等语法是一个很好的练习]
如果您研究了 Quentin 和我的回答,您应该能够看出其中的规律。我的例子在本质上也有些不平凡,所以我认为最好检查一下。
为了完全理解,这里是有效载荷的样子:
'tags' =>
array (
0 =>
array (
'id' => '2',
'name' => 'React JS',
),
1 =>
array (
'id' => '5',
'name' => 'JSON Web Tokens',
),
2 => 'Sandwiches',
),
可以看到有3个标签。前两个已经存在。第三个在我的数据库中不存在,所以它以字符串值而不是对象的形式出现。 Laravel/PHP 不喜欢嵌套对象“现有标签”,所以我不得不修改 FormData(它有图像,因此是多部分编码类型)。
对于上下文,这是我的整个 submitForm 函数:
const payload = new FormData();
Object.keys(this.example).forEach(field => payload.append(field, this.example[field]));
this.example.tags.forEach((tag, i) => {
if (Object.prototype.toString.call(tag) === '[object String]') {
payload.append(`tags[${i}]`, tag);
} else {
Object.keys(tag).forEach(field => payload.append(`tags[${i}][${field}]`, tag[field]));
}
});
this.example.images.forEach(image => payload.append('images[]', image));
const response = await axios.post(route('admin.examples.create'), payload);
参考资料:
Check if a variable is a string in JavaScript
我需要使用 ajax 和 FormData 发送一些数据,因为我想发送一个文件和一些其他参数。我通常发送数据的方式是这样的:
$.ajax({
type: 'POST',
url: 'some_url',
dataType: 'json',
processData:false,
contentType:false,
data:{
Lvl_1-1: 'something',
Lvl_1-2: 'something',
Lvl_1-3: {
Lvl_1-3-1: "something",
Lvl_1-3-2: "something",
Lvl_1-3-3: "something",
},
},
...
});
如果我不使用FormData(),我没有问题,但是当使用FormData()时,只有Lvl1上的数据是可以的,但是嵌套的任何东西都显示为这样的字符串
<b>array</b> <i>(size=3)</i>
'Lvl1-1' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>'Something'</font>
<i>(length=23)</i>
'Lvl1-2' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>''Something''</font> <i>(length=3)</i>
'Lvl1-3' <font color='#888a85'>=></font> <small>string</small>
<font color='#cc0000'>'[object Object]'</font> <i>(length=17)</i>
如果我使用 FormData() 对 Lvl1-3 中的数据进行编码,而不是 [object Object]
,我会得到 [object FormData]
如何在 Lvl1-3 上获取数组而不是字符串?
注意:如果文件位于顶层 (Lvl_1),我可以使用 FormData() 毫无问题地发送文件。我没有写附加文件的代码,因为那不是问题,嵌套数据才是。我刚刚提到了该文件,因为这就是我使用 FormData() 的原因。
URL 编码的表单数据没有任何原生方式来表达复杂的数据结构。它只支持简单的键=值对。
?foo=1&bar=2
大多数表单数据解析库允许使用同名键的数据数组
?foo=1&foo=2
PHP 在该格式之上添加了自己的语法:
?foo[]=1&foo[]=2
允许关联数组中的命名键:
?foo[bar]=1&foo[baz]=2
和嵌套数组:
?foo[bar][level2a]=1&foo[bar][level2b]=2
由于 PHP 的流行,jQuery 在将 JavaScript 对象传递给 data
时采用了生成表单数据的语法。
如果您想使用 FormData
,那么 jQuery 不会为您重新处理它。
您看到的效果是因为您试图将一个对象(我猜是一个 FormData 实例,但您没有显示代码的那一部分)作为 append
的第二个参数- 需要字符串的地方。
您需要自己使用 PHP 的语法生成键名。
form_data_instance.append("Lvl_1-3[Lvl_1-3-1]", "something");
form_data_instance.append("Lvl_1-3[Lvl_1-3-2]", "something");
form_data_instance.append("Lvl_1-3[Lvl_1-3-3]", "something");
在我这边,我将嵌套参数字符串化并在另一端解析它们。
例如,如果我想通过:
{"sthing":
{"sthing":"sthing"},
{"picture":
{"legend":"great legend"},
{"file":"great_picture.jpg"}
}
}
然后我做:
// On the client side
const nestedParams = {"sthing":
{"sthing":"sthing"},
{"picture":
{"legend":"great legend"}
}
};
const pictureFile = document.querySelector('input[type="file"]')[0];
const formDataInstance = new FormData;
formDataInstance.append("nested_params": JSON.stringify(nested_params);
formDataInstance.append("file": document.querySelector('input[type="file"]')[0]);
// On the server side
params["nested_params"] = JSON.parse(params["nested_params"]);
params["nested_params"]["sthing"]["picture"]["file"] = params["file"];
为了补充 Quentin 的回答,我有一些 PHP Laravel 代码是这样的:
$tags = [];
foreach ($request->input('tags') as $tag) {
if (!is_array($tag)) {
$new_tag = Tag::generate($tag);
array_push($tags, $new_tag->id);
} else {
array_push($tags, $tag['id']);
}
}
你可以看到我从一个空数组开始,然后用 $request->input('tags')
中的值填充它。该请求输入是一个多维数组,因此遇到了这个问题中描述的问题。 仅在使用FormData和multipart/form-data
表单编码类型时表现出来。
我能够通过 Quentin 的回答加上这里的客户端 JavaScript 代码修复它:
this.example.tags.forEach((tag, i) => {
if (Object.prototype.toString.call(tag) === '[object String]') {
payload.append(`tags[${i}]`, tag);
} else {
Object.keys(tag).forEach(field => payload.append(`tags[${i}][${field}]`, tag[field]));
}
});
此代码首先检查要添加到 FormData 的标签是否为字符串。如果是,则它是一个新的、不存在的标签。然后它通过它的索引号添加它。如果标签已经存在,我只发送客户拥有的所有值。在我的例子中,服务器只关心名称和 ID(对于与该行 ID 的 SQL 关系),所以这很好,但是使用 arr[index][field]
.[=18= 等语法是一个很好的练习]
如果您研究了 Quentin 和我的回答,您应该能够看出其中的规律。我的例子在本质上也有些不平凡,所以我认为最好检查一下。
为了完全理解,这里是有效载荷的样子:
'tags' =>
array (
0 =>
array (
'id' => '2',
'name' => 'React JS',
),
1 =>
array (
'id' => '5',
'name' => 'JSON Web Tokens',
),
2 => 'Sandwiches',
),
可以看到有3个标签。前两个已经存在。第三个在我的数据库中不存在,所以它以字符串值而不是对象的形式出现。 Laravel/PHP 不喜欢嵌套对象“现有标签”,所以我不得不修改 FormData(它有图像,因此是多部分编码类型)。
对于上下文,这是我的整个 submitForm 函数:
const payload = new FormData();
Object.keys(this.example).forEach(field => payload.append(field, this.example[field]));
this.example.tags.forEach((tag, i) => {
if (Object.prototype.toString.call(tag) === '[object String]') {
payload.append(`tags[${i}]`, tag);
} else {
Object.keys(tag).forEach(field => payload.append(`tags[${i}][${field}]`, tag[field]));
}
});
this.example.images.forEach(image => payload.append('images[]', image));
const response = await axios.post(route('admin.examples.create'), payload);
参考资料: Check if a variable is a string in JavaScript