在 AJAX 上发送嵌套的 FormData

Posted

技术标签:

【中文标题】在 AJAX 上发送嵌套的 FormData【英文标题】:Sending nested FormData on AJAX 【发布时间】:2015-04-30 17:40:12 【问题描述】:

我需要使用 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'>=&gt;</font> <small>string</small> 
        <font color='#cc0000'>'Something'</font> 
        <i>(length=23)</i>
    'Lvl1-2' <font color='#888a85'>=&gt;</font> <small>string</small> 
        <font color='#cc0000'>''Something''</font> <i>(length=3)</i>
    'Lvl1-3' <font color='#888a85'>=&gt;</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() 的原因。

【问题讨论】:

检查 github.com/foo123/serialiser.js 将复杂/嵌套的表单字段序列化为 formData、对象、json、url 编码的数据(作者) 【参考方案1】:

URL 编码的表单数据没有任何原生方式来表达复杂的数据结构。它只支持简单的 key=value 对。

?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 的流行,当您将 javascript 对象传递给 data 时,jQuery 采用了该语法来生成表单数据。

如果你想使用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");

【讨论】:

谢谢!这就是我一直在寻找的。现在我对这个问题有了更好的理解。【参考方案2】:

在我这一端,我将嵌套参数字符串化并在另一端解析它们。

例如,如果我想通过:

"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"];

【讨论】:

这仅在您处理 js 对象时有效(您可以使用类型 application/json 发送)。如果您需要发送二进制文件,它将无法正常工作,这是使用formdata over json的主要原因。 @ThierryJ。为什么这行不通? Freddo 基本上建议 formdata 用于发送二进制文件,正如您所建议的那样,并且还发送 JSON,他们基本上将其作为字符串发送...... @GrowinMan 这是一个非常好的问题 :) 老实说,我不记得我为什么这么说。也许我只阅读了答案的开头而不是阅读代码。在答案的当前状态下,它确实应该按要求工作。【参考方案3】:

为了补充 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-&gt;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(用于 SQL 与该行 ID 的关系),所以这很好,但是使用诸如 arr[index][field] 之类的语法是一个很好的练习。

如果您研究昆汀和我的答案,您应该能够看到这种模式。我的例子在本质上也有点不重要,所以我认为检查一下会很好。

为了完全理解,下面是有效载荷的样子:

  '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嵌套Ajax的模版

嵌套的ajax请求

拥抱基于jquery.deferred的ajax,和层层嵌套回调的ajax说拜拜

不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

如何通过 AJAX 将 jquery 对象数据发送到 php?

具有依赖 AJAX 调用的嵌套 Select2