将JS对象转换为表单数据

我怎样才能将我的JS对象转换为FormData?

我这样做的原因是,我有一个用~100个表单字段值构造的对象。

var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}

现在我被要求将上传文件功能添加到我的表单中,这当然是不可能通过JSON实现的,所以我计划移动到FormData。那么是否有任何方法可以将我的JS对象转换为FormData?

368423 次浏览

如果您有一个对象,您可以轻松地创建一个FormData对象,并将该对象的名称和值附加到FormData。

你还没有发布任何代码,所以这是一个通用的例子;

var form_data = new FormData();


for ( var key in item ) {
form_data.append(key, item[key]);
}


$.ajax({
url         : 'http://example.com/upload.php',
data        : form_data,
processData : false,
contentType : false,
type: 'POST'
}).done(function(data){
// do stuff
});

< >强MDN < / >强的文档中有更多的例子

抱歉这么晚才回答,我一直在纠结这个问题,因为Angular 2目前不支持文件上传。所以,这样做的方法是用FormData发送XMLHttpRequest。我创建了一个函数来做这个。我正在使用打印稿。要将其转换为Javascript,只需删除数据类型声明。

/**
* Transforms the json data into form data.
*
* Example:
*
* Input:
*
* fd = new FormData();
* dob = {
*  name: 'phone',
*  photos: ['myphoto.jpg', 'myotherphoto.png'],
*  price: '615.99',
*  color: {
*      front: 'red',
*      back: 'blue'
*  },
*  buttons: ['power', 'volup', 'voldown'],
*  cameras: [{
*      name: 'front',
*      res: '5Mpx'
*  },{
*      name: 'back',
*      res: '10Mpx'
*  }]
* };
* Say we want to replace 'myotherphoto.png'. We'll have this 'fob'.
* fob = {
*  photos: [null, <File object>]
* };
* Say we want to wrap the object (Rails way):
* p = 'product';
*
* Output:
*
* 'fd' object updated. Now it will have these key-values "<key>, <value>":
*
* product[name], phone
* product[photos][], myphoto.jpg
* product[photos][], <File object>
* product[color][front], red
* product[color][back], blue
* product[buttons][], power
* product[buttons][], volup
* product[buttons][], voldown
* product[cameras][][name], front
* product[cameras][][res], 5Mpx
* product[cameras][][name], back
* product[cameras][][res], 10Mpx
*
* @param {FormData}  fd  FormData object where items will be appended to.
* @param {Object}    dob Data object where items will be read from.
* @param {Object =   null} fob File object where items will override dob's.
* @param {string =   ''} p Prefix. Useful for wrapping objects and necessary for internal use (as this is a recursive method).
*/
append(fd: FormData, dob: Object, fob: Object = null, p: string = ''){
let apnd = this.append;


function isObj(dob, fob, p){
if(typeof dob == "object"){
if(!!dob && dob.constructor === Array){
p += '[]';
for(let i = 0; i < dob.length; i++){
let aux_fob = !!fob ? fob[i] : fob;
isObj(dob[i], aux_fob, p);
}
} else {
apnd(fd, dob, fob, p);
}
} else {
let value = !!fob ? fob : dob;
fd.append(p, value);
}
}


for(let prop in dob){
let aux_p = p == '' ? prop : `${p}[${prop}]`;
let aux_fob = !!fob ? fob[prop] : fob;
isObj(dob[prop], aux_fob, aux_p);
}
}

使用ES6和更函数式的编程方法@adeneo的答案可能是这样的:

function getFormData(object) {
const formData = new FormData();
Object.keys(object).forEach(key => formData.append(key, object[key]));
return formData;
}

或者使用.reduce()和arrow函数:

const getFormData = object => Object.keys(object).reduce((formData, key) => {
formData.append(key, object[key]);
return formData;
}, new FormData());

我有一个场景,在构造表单数据时,嵌套的JSON必须以线性方式序列化,因为这是服务器期望值的方式。所以,我写了一个小的递归函数来翻译JSON,就像这样:

{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress":{
"city":"Wonderland",
"code":"8796682911767",
"firstname":"Raj Pawan",
"lastname":"Gumdal",
"line1":"Addr Line 1",
"line2":null,
"state":"US-AS",
"region":{
"isocode":"US-AS"
},
"zip":"76767-6776"
}
}

变成这样:

{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress.city":"Wonderland",
"billingAddress.code":"8796682911767",
"billingAddress.firstname":"Raj Pawan",
"billingAddress.lastname":"Gumdal",
"billingAddress.line1":"Addr Line 1",
"billingAddress.line2":null,
"billingAddress.state":"US-AS",
"billingAddress.region.isocode":"US-AS",
"billingAddress.zip":"76767-6776"
}

服务器将接受这种转换格式的表单数据。

函数如下:

function jsonToFormData (inJSON, inTestJSON, inFormData, parentKey) {
// http://stackoverflow.com/a/22783314/260665
// Raj: Converts any nested JSON to formData.
var form_data = inFormData || new FormData();
var testJSON = inTestJSON || {};
for ( var key in inJSON ) {
// 1. If it is a recursion, then key has to be constructed like "parent.child" where parent JSON contains a child JSON
// 2. Perform append data only if the value for key is not a JSON, recurse otherwise!
var constructedKey = key;
if (parentKey) {
constructedKey = parentKey + "." + key;
}


var value = inJSON[key];
if (value && value.constructor === {}.constructor) {
// This is a JSON, we now need to recurse!
jsonToFormData (value, testJSON, form_data, constructedKey);
} else {
form_data.append(constructedKey, inJSON[key]);
testJSON[constructedKey] = inJSON[key];
}
}
return form_data;
}

调用:

        var testJSON = {};
var form_data = jsonToFormData (jsonForPost, testJSON);

我使用testJSON只是为了查看转换后的结果,因为我无法提取form_data的内容。AJAX post call:

        $.ajax({
type: "POST",
url: somePostURL,
data: form_data,
processData : false,
contentType : false,
success: function (data) {
},
error: function (e) {
}
});

该函数将object中的所有数据添加到FormData中

ES6版本来自@developer033:

function buildFormData(formData, data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key => {
buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;


formData.append(parentKey, value);
}
}


function jsonToFormData(data) {
const formData = new FormData();
  

buildFormData(formData, data);
  

return formData;
}


const my_data = {
num: 1,
falseBool: false,
trueBool: true,
empty: '',
und: undefined,
nullable: null,
date: new Date(),
name: 'str',
another_object: {
name: 'my_name',
value: 'whatever'
},
array: [
{
key1: {
name: 'key1'
}
}
]
};


jsonToFormData(my_data)

jQuery版本:

function appendFormdata(FormData, data, name){
name = name || '';
if (typeof data === 'object'){
$.each(data, function(index, value){
if (name == ''){
appendFormdata(FormData, value, index);
} else {
appendFormdata(FormData, value, name + '['+index+']');
}
})
} else {
FormData.append(name, data);
}
}




var formData = new FormData(),
your_object = {
name: 'test object',
another_object: {
name: 'and other objects',
value: 'whatever'
}
};
appendFormdata(formData, your_object);

其他的答案对我来说是不完整的。我从@Vladimir Novopashin的回答开始修改。以下是我需要的东西和我发现的bug:

  • 文件支持
  • 支持数组
  • 错误:复杂对象内的文件需要使用.prop而不是[prop]添加。例如,formData.append('photos[0][file]', file)不能在谷歌chrome上工作,而 李formData.append('photos[0].file', file)工作< / >
  • 忽略对象中的某些属性

下面的代码应该可以在IE11和常绿浏览器上运行。

function objectToFormData(obj, rootName, ignoreList) {
var formData = new FormData();


function appendFormData(data, root) {
if (!ignore(root)) {
root = root || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
appendFormData(data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
appendFormData(data[key], key);
} else {
appendFormData(data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}
}


function ignore(root){
return Array.isArray(ignoreList)
&& ignoreList.some(function(x) { return x === root; });
}


appendFormData(obj, rootName);


return formData;
}

试试obj2fd => https://www.npmjs.com/package/obj2fd

import obj2fd from 'obj2fd'


let data = {a:1, b:2, c:{ca:1}};
let dataWithFormData = obj2fd(data);
//result => [a=>1, b=>2, c=>[ca=>1]]

尝试JSON。函数如下所示

var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );

这个方法将一个JS对象转换为一个FormData:

function convertToFormData(params) {
return Object.entries(params)
.reduce((acc, [key, value]) => {
if (Array.isArray(value)) {
value.forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else if (typeof value === 'object' && !(value instanceof File) && !(value instanceof Date)) {
Object.entries(value).forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else {
acc.append(key, value);
}


return acc;
}, new FormData());
}

在我的例子中,我的对象也有属性,即文件数组。因为它们是二进制的,所以应该以不同的方式处理——索引不需要是键的一部分。所以我修改了@Vladimir Novopashin和@developer033的答案:

export function convertToFormData(data, formData, parentKey) {
if(data === null || data === undefined) return null;


formData = formData || new FormData();


if (typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key =>
convertToFormData(data[key], formData, (!parentKey ? key : (data[key] instanceof File ? parentKey : `${parentKey}[${key}]`)))
);
} else {
formData.append(parentKey, data);
}


return formData;
}

你可以简单地安装qs:

npm i qs

简单的导入:

import qs from 'qs'

将对象传递给qs.stringify():

var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}


qs.stringify(item)

递归地

const toFormData = (f => f(f))(h => f => f(x => h(h)(f)(x)))(f => fd => pk => d => {
if (d instanceof Object) {
Object.keys(d).forEach(k => {
const v = d[k]
if (pk) k = `${pk}[${k}]`
if (v instanceof Object && !(v instanceof Date) && !(v instanceof File)) {
return f(fd)(k)(v)
} else {
fd.append(k, v)
}
})
}
return fd
})(new FormData())()


let data = {
name: 'John',
age: 30,
colors: ['red', 'green', 'blue'],
children: [
{ name: 'Max', age: 3 },
{ name: 'Madonna', age: 10 }
]
}
console.log('data', data)
document.getElementById("data").insertAdjacentHTML('beforeend', JSON.stringify(data))


let formData = toFormData(data)


for (let key of formData.keys()) {
console.log(key, formData.getAll(key).join(','))
document.getElementById("item").insertAdjacentHTML('beforeend', `<li>${key} = ${formData.getAll(key).join(',')}</li>`)
}
<p id="data"></p>
<ul id="item"></ul>

我使用这个Post我的对象数据作为表单数据。

const encodeData = require('querystring');


const object = {type: 'Authorization', username: 'test', password: '123456'};


console.log(object);
console.log(encodeData.stringify(object));

也许你正在寻找这个,一个代码,接收你的javascript对象,从它创建一个FormData对象,然后使用新的获取API将它POST到你的服务器:

    let myJsObj = {'someIndex': 'a value'};


let datos = new FormData();
for (let i in myJsObj){
datos.append( i, myJsObj[i] );
}


fetch('your.php', {
method: 'POST',
body: datos
}).then(response => response.json())
.then(objson => {
console.log('Success:', objson);
})
.catch((error) => {
console.error('Error:', error);
});
我从Gudradain的回答引用这个。我用Typescript格式编辑了一下。

class UtilityService {
private appendFormData(formData, data, rootName) {


let root = rootName || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
this.appendFormData(formData, data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
this.appendFormData(formData, data[key], key);
} else {
this.appendFormData(formData, data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}


getFormDataFromObj(data) {
var formData = new FormData();


this.appendFormData(formData, data, '');


return formData;
}
}


export let UtilityMan = new UtilityService();

我可能有点晚了,但这就是我创建的将单个对象转换为FormData的方法。

function formData(formData, filesIgnore = []) {
let data = new FormData();


let files = filesIgnore;


Object.entries(formData).forEach(([key, value]) => {
if (typeof value === 'object' && !files.includes(key)) {
data.append(key, JSON.stringify(value) || null);
} else if (files.includes(key)) {
data.append(key, value[0] || null);
} else {
data.append(key, value || null);
}
})


return data;
}

它是如何工作的? 它将转换并返回所有属性,除了您在忽略列表中设置的File对象(第二个参数)。如果有人能告诉我一个更好的方法来确定这将有帮助!)变成一个json字符串使用JSON.stringify。然后在你的服务器上,你只需要把它转换回JSON对象

例子:

let form = {
first_name: 'John',
last_name: 'Doe',
details: {
phone_number: 1234 5678 910,
address: '123 Some Street',
},
profile_picture: [object FileList] // set by your form file input. Currently only support 1 file per property.
}


function submit() {
let data = formData(form, ['profile_picture']);


axios.post('/url', data).then(res => {
console.log('object uploaded');
})
}

我仍然有点新的Http请求和JavaScript,所以任何反馈将高度赞赏!

这里有一个使用Object.entries()的简短而甜蜜的解决方案,它甚至可以照顾到你的嵌套对象。

// If this is the object you want to convert to FormData...
const item = {
description: 'First item',
price: 13,
photo: File
};


const formData = new FormData();


Object.entries(item).forEach(([key, value]) => {
formData.append(key, value);
});


// At this point, you can then pass formData to your handler method

在这里阅读更多关于Object.entries() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries

2022年更新

Axios现在支持将对象自动序列化为FormData

从0.27版开始,如果请求的Content-Type报头被设置为multipart/form-data, Axios就支持将对象自动序列化为FormData对象。阅读更多

嵌套对象和文件

下面的解决方案处理嵌套对象、数组和文件。

const buildFormData = (formData: FormData, data: FormVal, parentKey?: string) => {
if (Array.isArray(data)) {
data.forEach((el) => {
buildFormData(formData, el, parentKey)
})


} else if (typeof data === "object" && !(data instanceof File)) {
Object.keys(data).forEach((key) => {
buildFormData(formData, (data as FormDataNest)[key], parentKey ? `${parentKey}.${key}` : key)
})


} else {
if (isNil(data)) {
return
}


let value = typeof data === "boolean" || typeof data === "number" ? data.toString() : data
formData.append(parentKey as string, value)
}
}


export const getFormData = (data: Record<string, FormDataNest>) => {
const formData = new FormData()


buildFormData(formData, data)


return formData
}

类型

type FormDataPrimitive = string | Blob | number | boolean


interface FormDataNest {
[x: string]: FormVal
}


type FormVal = FormDataNest | FormDataPrimitive

你可以简单地使用:

formData.append('item', JSON.stringify(item));

很简单,可以这样做:

var item: { some1: "ajbd" , some2: "dds".. }
let myFormData = new FormData();
      

const abc = item.some1;
const xyz = item.some2;


myFormData.append('field1', abc);
myFormData.append('field2', xyz);
    

fetch('http:url', {
method: 'POST',
headers: {
'Content-Type': false,
},
body: myFormData,
}).
do promise ..

function toFormData(o) {
return Object.entries(o).reduce((d,e) => (d.append(...e),d), new FormData())
}


var object = {
username: 'JohnDoe',
file: new File(['foo'], 'foo.txt', {type: 'text/plain'})
}


fetch('https://httpbin.org/post', {
method: 'POST',
body: toFormData(object)
}).then(r => r.json()).then(console.log)

请注意:这个答案没有直接回答问题,而是给出了说明和替代方案。

人们通常使用FormData来允许他们上传文件,有3种方法可以做到这一点,我会在这个回答中详细提到它们。

1. 直接从表单中获取数据

为此,你需要在每个输入中有namevalue属性,然后告诉FormData从表单DOM中获取值;

let formData = new FormData(data.target as HTMLFormElement)
  • 优点:
  1. 你不需要使用JS解决方案来获取这些值。
  2. 原生支持嵌套数据,数组等…
  • 缺点:
  1. 您需要确保所有需要的数据都作为值属性添加到输入dom上
< p > 2. 用JS转换数据
如果值存储在变量中,则可以使用JS将它们附加到FormData
  • 优点:
  1. 是否可以根据需要进行操作
  2. 你不需要在输入dom中有值属性
  • 缺点:
  1. 自定义复杂JS将嵌套数据追加到FormData对象
  2. 您需要将值存储在一个变量中

3.在单独的请求中上传文件

您可以为文件上传输入创建一个单独的表单。

  • 优点:
  1. 您不需要等待上传提交
  2. 你不需要担心将所有数据追加到FormData对象,你只追加文件。
  • 缺点:
  1. 在创建实体本身之前,您需要在后端处理这些上传
  2. 您需要在前端和后端处理删除文件

使用jquery,你可以通过$.param(obj) 简单地做到这一点。

< p >的例子:
 const obj = {
description: 'Some Item',
price: '0.00',
srate: '0.00',
color: 'red'
}


const form_obj = $.param(obj);


$.ajax({
url:"example.com",
method:"POST",
data:form_obj
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

将处理嵌套数组和对象的解决方案。有人可能会觉得有用

             function add_form_data(form_data,key,item,arr){
if(typeof(item)==='object' && item && item.constructor===Array){
for(var i=0;i<item.length;i++){
var item2=item[i];
var key2=key+'[' + i +']';
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,true);
}
}else if(typeof(item)==='object' && item){
for ( var key2 in item ) {
var item2=item[key2];
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,arr);
}
}else{
form_data.append(key,item);
}
}

使用

            var form_data = new FormData();
            

add_form_data(form_data,null,json_data);// provide json_data here
             

var string_url_data=new URLSearchParams(form_data).toString();// if query string is needed

下面是一个非常简单的TypeScript实现,基于@Vladimir Novopashin和@developer033的回答。打印稿操场

type Serializeable =
| string
| number
| boolean
| null
| Date
| File
| { [x: string | number]: Serializeable }
| Array<Serializeable>;


function serialize(
data: Serializeable,
parentKey = '',
formData: FormData = new FormData()
): FormData {
if ( typeof data === 'string') {
formData.append(parentKey, data);
} else if ( typeof data === 'number') {
formData.append(parentKey, data.toString());
} else if ( typeof data === 'boolean') {
formData.append(parentKey, data ? 'true' : 'false');
} else if (data === null) {
formData.append(parentKey, 'null');
} else if (data instanceof Date) {
formData.append(parentKey, data.toISOString());
} else if (data instanceof File) {
formData.append(parentKey, data);
} else {
// Arrays and objects
Object.entries(data).forEach((entry: [string | number, Serializeable]) => {
const [key, value] = entry;
serialize(value, parentKey ? `${parentKey}[${key}]` : key.toString(), formData);
});
}


return formData;
}