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

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

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

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


当前回答

下面是一个非常简单的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;
}

其他回答

抱歉这么晚才回答,我一直在纠结这个问题,因为Angular 2目前不支持文件上传。方法是用FormData发送一个XMLHttpRequest。我创建了一个函数来做这个。我用的是Typescript。要将其转换为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);
        }
    }

我可能有点晚了,但这就是我创建的将单个对象转换为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,所以任何反馈将高度赞赏!

下面是一个非常简单的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;
}

在我的例子中,我的对象也有属性,即文件数组。因为它们是二进制的,所以应该以不同的方式处理——索引不需要是键的一部分。所以我修改了@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;
}

我有一个场景,在构造表单数据时,嵌套的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) {
            }
        });