将数组添加到FormData并通过AJAX发送

202

我正在使用ajax提交一个包含数组、文本字段和文件的多部分表单。

我将每个VAR追加到主数据中,如下所示

var attachments = document.getElementById('files'); 
var data= new FormData();

for (i=0; i< attachments.files.length; i++){
    data.append('file', attachments.files[i]);
    console.log(attachments.files[i]);

    data.append ('headline', headline);
    data.append ('article', article);
    data.append ('arr', arr);
    data.append ('tag', tag);

然后我使用ajax函数将其发送到一个PHP文件以存储在SQL数据库中。

$.ajax({    
    type: "post",
    url: 'php/submittionform.php',
    cache: false,
    processData: false,
    contentType: false,
    data: data,
    success: function(request) {$('#box').html(request); }
})

但在PHP方面,数组变量arr会显示为字符串。

当我不使用ajax发送它作为表单数据,而是使用简单的$.POST选项时,我确实可以在PHP方面将其作为数组得到,但这样我就无法同时发送文件。

有什么解决办法吗?

16个回答

401

你也可以通过这种方式使用 FormData 发送数组:

var formData = new FormData;
var arr = ['this', 'is', 'an', 'array'];

for (var i = 0; i < arr.length; i++) {
  formData.append('arr[]', arr[i]);
}

console.log(...formData);

所以,您可以像处理简单的HTML表单一样编写arr []。在PHP的情况下,它应该有效。

您可能会发现这篇文章有用:如何在查询字符串中传递数组?


2
@Oleg,为什么在 formData.append('arr[]', arr[i]); 中需要写 arr[]?为什么不是 arr 就可以了呢?我尝试了两种方式,但只有 arr[] 能够正常工作。 - Totoro
@Totoro 因为在 arr 的情况下,你只是在每次循环迭代中重新定义这个值,在最后,最终值将等于最后一个数组元素,而不是整个数组。 - Oleg
@Oleg 如果重新定义是情况,那么arr[]有什么不同,为什么arr[]没有被重新定义?arr[]也是一个字符串。在我的测试中,无论是arr还是arr[]都没有被重新定义。我使用相同的键但不同的值在FormData中得到了多个数组。所以我得到了一个值为1arr和另一个值为2arr - Totoro
8
如果有人想要发布一个对象数组,你可以按照下面的方式扩展这个答案:for (var i = 0; i < myArr; i++) { var myItemInArr = myArr[i]; for (var prop in myItemInArr) { fileData.append(`myArr[${i}][${prop}]`, myItemInArr[prop]); } } - edqwerty
@t 我认为这是规范的一部分。 - The-null-Pointer-
显示剩余4条评论

152

您有几个选项:

将其转换为JSON字符串,然后在PHP中解析它(推荐)

JS

var json_arr = JSON.stringify(arr);
PHP
$arr = json_decode($_POST['arr']);

或者使用@Curios的方法

通过FormData发送数组。


不推荐:使用序列化方式将数据传输到PHP中再反序列化

JS

// Use <#> or any other delimiter you want
var serial_arr = arr.join("<#>"); 

PHP

$arr = explode("<#>", $_POST['arr']);

1
问题在于数组包含真实文本行,带有空格和标点符号。我不想搞砸它。 - shultz
4
当您使用JSON进行编码和解析时,数据不会丢失。试试看吧 ;) - Richard de Wit
1
如果你有像文件或FormData这样的数据,你在使用json.stringfy时会失去它们。 - Mohsen
1
我更喜欢字符串化,更简单。虽然你需要使用[]来传递数组的数组进行某种递归,但知道可以这样做还是很好的。 - Chopnut
1
为什么这个回答被接受了,当原帖中的关键词不仅没有列出PHP,而且以JavaScript开头? - JurgenBlitz
显示剩余3条评论

16

Typescript 版本:

export class Utility {      
    public static convertModelToFormData(model: any, form: FormData = null, namespace = ''): FormData {
        let formData = form || new FormData();
        let formKey;

        for (let propertyName in model) {
            if (!model.hasOwnProperty(propertyName) || !model[propertyName]) continue;
            let formKey = namespace ? `${namespace}[${propertyName}]` : propertyName;
            if (model[propertyName] instanceof Date)
                formData.append(formKey, model[propertyName].toISOString());
            else if (model[propertyName] instanceof Array) {
                model[propertyName].forEach((element, index) => {
                    const tempFormKey = `${formKey}[${index}]`;
                    this.convertModelToFormData(element, formData, tempFormKey);
                });
            }
            else if (typeof model[propertyName] === 'object' && !(model[propertyName] instanceof File))
                this.convertModelToFormData(model[propertyName], formData, formKey);
            else
                formData.append(formKey, model[propertyName].toString());
        }
        return formData;
    }
}

使用:

let formData = Utility.convertModelToFormData(model);

1
非常感谢,它运行得很好!对于那些不使用TypeScript的人,您只需要删除第一行和最后一行+将方法声明“public static”替换为“function”+删除类型声明“:any”,“:FormData”和“:FormData”。 - coccoinomane
目前,该函数跳过零和空字符串。要包括它们,请将第7行的if块替换为:if (!model.hasOwnProperty(propertyName) || (!model[propertyName] && model[propertyName]!==0 && model[propertyName]!=='')) continue; - coccoinomane

8

这是一个老问题,但我最近遇到了在发布对象时与文件一起发布的问题。我需要能够发布一个带有子属性(包括对象和数组)的对象。

下面的函数将遍历一个对象并创建正确的formData对象。

// formData - instance of FormData object
// data - object to post
function getFormData(formData, data, previousKey) {
  if (data instanceof Object) {
    Object.keys(data).forEach(key => {
      const value = data[key];
      if (value instanceof Object && !Array.isArray(value)) {
        return this.getFormData(formData, value, key);
      }
      if (previousKey) {
        key = `${previousKey}[${key}]`;
      }
      if (Array.isArray(value)) {
        value.forEach(val => {
          formData.append(`${key}[]`, val);
        });
      } else {
        formData.append(key, value);
      }
    });
  }
}

这将转换以下JSON -
{
  name: 'starwars',
  year: 1977,
  characters: {
    good: ['luke', 'leia'],
    bad: ['vader'],
  },
}

将以下内容转换为FormData格式。
 name, starwars
 year, 1977
 characters[good][], luke
 characters[good][], leia
 characters[bad][], vader

对我来说很有用,只需要在append内部应用String(value)即可(否则对于true/false会失败)。此外,它应该是(value !== null) && formData.append(key, value)而不仅仅是formData.append(key, value),否则它会在null值上失败。 - Alexander

5

将所有类型的输入添加到FormData中

const formData = new FormData();
for (let key in form) {
    Array.isArray(form[key])
        ? form[key].forEach(value => formData.append(key + '[]', value))
        : formData.append(key, form[key]) ;
}

5

这是另一个版本的convertModelToFormData,因为我需要它也能够发送文件。

utility.js

const Utility = {
  convertModelToFormData(val, formData = new FormData, namespace = '') {
    if ((typeof val !== 'undefined') && val !== null) {
      if (val instanceof Date) {
        formData.append(namespace, val.toISOString());
      } else if (val instanceof Array) {
        for (let i = 0; i < val.length; i++) {
          this.convertModelToFormData(val[i], formData, namespace + '[' + i + ']');
        }
      } else if (typeof val === 'object' && !(val instanceof File)) {
        for (let propertyName in val) {
          if (val.hasOwnProperty(propertyName)) {
            this.convertModelToFormData(val[propertyName], formData, namespace ? `${namespace}[${propertyName}]` : propertyName);
          }
        }
      } else if (val instanceof File) {
        formData.append(namespace, val);
      } else {
        formData.append(namespace, val.toString());
      }
    }
    return formData;
  }
}
export default Utility;

my-client-code.js

import Utility from './utility'
...
someFunction(form_object) {
  ...
  let formData = Utility.convertModelToFormData(form_object);
  ...
}

你是我的英雄。 - Winteriscoming

3

将三种数据格式转换为 FormData

1. 单个值,如字符串、数字或布尔值

 let sampleData = {
  activityName: "Hunting3",
  activityTypeID: 2,
  seasonAssociated: true, 
};

2. 数组转为对象数组

let sampleData = {
   activitySeason: [
    { clientId: 2000, seasonId: 57 },
    { clientId: 2000, seasonId: 57 },
  ],
};

3. 持有键值对的对象

let sampleData = {
    preview: { title: "Amazing World", description: "Here is description" },
};

让我们生活变得更加轻松的技术:
function transformInToFormObject(data) {
  let formData = new FormData();
  for (let key in data) {
    if (Array.isArray(data[key])) {
      data[key].forEach((obj, index) => {
        let keyList = Object.keys(obj);
        keyList.forEach((keyItem) => {
          let keyName = [key, "[", index, "]", ".", keyItem].join("");
          formData.append(keyName, obj[keyItem]);
        });
      });
    } else if (typeof data[key] === "object") { 
      for (let innerKey in data[key]) {
        formData.append(`${key}.${innerKey}`, data[key][innerKey]);
      }
    } else {
      formData.append(key, data[key]);
    }
  }
  return formData;
}

示例: 输入数据

let sampleData = {
  activityName: "Hunting3",
  activityTypeID: 2,
  seasonAssociated: true,
  activitySeason: [
    { clientId: 2000, seasonId: 57 },
    { clientId: 2000, seasonId: 57 },
  ],
  preview: { title: "Amazing World", description: "Here is description" },
};

输出FormData:

activityName: Hunting3
activityTypeID: 2
seasonAssociated: true
activitySeason[0].clientId: 2000
activitySeason[0].seasonId: 57
activitySeason[1].clientId: 2000
activitySeason[1].seasonId: 57
preview.title: Amazing World
preview.description: Here is description

3
基于@YackY的回答,以下是更简短的递归版本:

"最初的回答"
function createFormData(formData, key, data) {
    if (data === Object(data) || Array.isArray(data)) {
        for (var i in data) {
            createFormData(formData, key + '[' + i + ']', data[i]);
        }
    } else {
        formData.append(key, data);
    }
}

使用示例:

var data = {a: '1', b: 2, c: {d: '3'}};
var formData = new FormData();
createFormData(formData, 'data', data);

Sent data:

data[a]=1&
data[b]=2&
data[c][d]=3

2
下一个版本适用于包含简单值数组的模型:
function convertModelToFormData(val, formData = new FormData(), namespace = '') {
    if((typeof val !== 'undefined') && (val !== null)) {
        if(val instanceof Date) {
            formData.append(namespace, val.toISOString());
        } else if(val instanceof Array) {
            for(let element of val) {
                convertModelToFormData(element, formData, namespace + '[]');
            }
        } else if(typeof val === 'object' && !(val instanceof File)) {
            for (let propertyName in val) {
                if(val.hasOwnProperty(propertyName)) {
                    convertModelToFormData(val[propertyName], formData, namespace ? namespace + '[' + propertyName + ']' : propertyName);
                }
            }
        } else {
            formData.append(namespace, val.toString());
        }
    }
    return formData;
}

2
如果您有嵌套的对象和数组,最好使用递归来填充FormData对象。"最初的回答"。
function createFormData(formData, data, key) {
    if ( ( typeof data === 'object' && data !== null ) || Array.isArray(data) ) {
        for ( let i in data ) {
            if ( ( typeof data[i] === 'object' && data[i] !== null ) || Array.isArray(data[i]) ) {
                createFormData(formData, data[i], key + '[' + i + ']');
            } else {
                formData.append(key + '[' + i + ']', data[i]);
            }
        }
    } else {
        formData.append(key, data);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接