在AJAX上发送嵌套的FormData

时间:2015-02-27 21:38:12

标签: ajax string nested form-data

我需要使用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()的原因。

3 个答案:

答案 0 :(得分:18)

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的普及,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");

答案 1 :(得分:6)

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

例如,如果我想通过:

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

答案 2 :(得分:0)

要添加到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]这样的语法是一个很好的练习。

如果您研究昆汀和我的答案,您应该能够看到这种模式。我的示例本质上也不是小事,因此我认为最好进行研究。

要完全理解,这是有效载荷的样子:

  '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