Converter objeto JS em dados de formulário


128

Como posso converter meu objeto JS para FormData?

O motivo pelo qual quero fazer isso é porque tenho um objeto que construí com os cerca de 100 valores de campo de formulário.

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

Agora fui solicitado a adicionar a funcionalidade de upload de arquivo ao meu formulário, o que, obviamente, é impossível via JSON e, portanto, estou planejando mudar para FormData. Existe alguma maneira de converter meu objeto JS FormData?


você pode compartilhar seu trabalho / progresso?
Ritikesh 01 de

que tal JSON.stringify ()?
Sunny Sharma,

1
@Sunny - Isso produzirá um texto JSON em uma string. Isso não é um FormDataobjeto.
Quentin de

Sim, você pode, você pode anexar a objetos formData.
adeneo de

você pode nos mostrar o que você quer dizer com FormData? algum formato específico?
Sunny Sharma,

Respostas:


153

Se você tiver um objeto, poderá criar facilmente um objeto FormData e anexar os nomes e valores desse objeto a formData.

Você não postou nenhum código, então é um exemplo geral;

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
});

Existem mais exemplos na documentação do MDN


3
@Lior - itemé um objeto regular criado pelo OP, então não deve ter nenhuma propriedade que não seja sua, a menos que alguém cometa o erro de prototipar algo no construtor de objeto, caso em que você estaria em um mundo de problemas , e não é algo contra o qual devemos ter que nos proteger.
adeneo

2
@Lior - é apenas adicionar os pares de chave / valor ao FormData, adicionar uma propriedade prototipada não vai quebrar nada e usar Object.keysnão é a resposta, já que você não deve ter que obter as chaves como um array e, em seguida, iterar sobre as chaves para obter os valores, você deve usar um for..inloop.
adeneo

2
Claro que vai, você não sabe o que o servidor está esperando ... pois ... em JS é problemático, a solução não precisa ser Object.keys (), poderia ser hasOwnProperty (), mas precisa ser pelo menos um aviso.
Lior

3
@Lior - Se o seu servidor quebrar ao receber mais um par de chave / valor em uma solicitação POST, você está fazendo isso errado. Acho que a resposta está correta e não vou alterá-la para uso Object.keysou hasOwnProperty()porque o objeto está postado na pergunta e não deve precisar de nenhum desses. A razão pela qual você às vezes vê sendo hasOwnPropertyusados ​​em plug-ins etc. é porque você nunca sabe o que algumas pessoas podem fazer com o Objectconstrutor, mas na maioria das vezes as pessoas não deveriam ter que testar propriedades herdadas em objetos que criaram, isso é um sinal de que provavelmente você está fazendo algo errado.
adeneo

5
@Lior, você pretende construir aviões de palha a seguir, esperando que isso atraia mais aviões de verdade que vão jogar comida do céu? É importante entender por que uma verificação hasOwnProperty é usada, apenas dizendo que as coisas são consideradas "melhores práticas" porque você leu o livro de alguém (supondo, Crockford) não vai muito longe, tentando educar um colega So membro com mais de 100 vezes a reputação e 100 vezes o número de respostas que você tem também não ajuda muito no seu ponto. Além disso, nomeie uma nova biblioteca de terceiros que altere o protótipo? Essa postagem é de um momento diferente ...
Benjamin Gruenbaum

83

Com ES6 e uma abordagem de programação mais funcional, a resposta de @adeneo poderia ser assim:

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

E, alternativamente, usando .reduce()as funções e seta:

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

44

Esta função adiciona todos os dados do objeto ao FormData

Versão ES6 de @ 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)

versão 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);

Bom, continue assim
Vivek Doshi,

Funciona muito bem! Obrigado! Também tive que adicionar && !(data instanceof Blob)no meu caso para fazer o upload das minhas imagens
Clément Baconnier

Funciona bem para mim, adicionei if (typeof data === 'object' && data! == null) {porque estava lançando uma exceção se o valor for null
quase

Para a versão ES6, adicionei && !(Array.isArray(data) && !data.length)a condição "se" ou o array vazio seria removido.
Mtxz

14

As outras respostas estavam incompletas para mim. Comecei a partir da resposta @Vladimir Novopashin e modifiquei. Aqui estão as coisas que eu precisava e o bug que encontrei:

  • Suporte para arquivo
  • Suporte para array
  • Bug: O arquivo dentro de um objeto complexo precisa ser adicionado com em .propvez de [prop]. Por exemplo, formData.append('photos[0][file]', file)não funcionou no google chrome, enquanto formData.append('photos[0].file', file)trabalhava
  • Ignorar algumas propriedades em meu objeto

O código a seguir deve funcionar no IE11 e navegadores permanentes.

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;
}

1
A única resposta que suporta arrays, objetos e arquivos.
SOTN

Olá, por que você adiciona o arquivo à raiz? É possível adicionar ao filho também?
Cedric Arnould

@CedricArnould Pode ser um mal-entendido, mas o método é recursivo, portanto, mesmo que seja escrito formData.append(root, data), não significa que foi adicionado à raiz.
Gudradain

Eu entendo sua resposta, estranhamente quando obtenho o resultado no servidor, tenho uma coleção exclusiva de arquivos e os dados. Mas posso ver em um arquivo o nome que dá a informação a qual criança está conectado. Talvez o problema venha do .Net Core e como ele gerencia o upload de arquivos. Obrigado pela sua resposta.
Cedric Arnould

estou tentando usar isso, mas não há exemplo de uso. o formato esperado do parâmetro ignoreList seria muito útil.
jpro

8

Experimente a função JSON.stringify conforme abaixo

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

1
Esta é a melhor forma de o conseguir.
Rob de

ele continua anexando o json após várias vezes de depuração de erros
Snow Bases

7

Tive um cenário em que o JSON aninhado precisava ser serializado de maneira linear enquanto os dados do formulário eram construídos, pois é assim que o servidor espera valores. Então, escrevi uma pequena função recursiva que traduz o JSON que é assim:

{
   "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"
   }
}

Em algo assim:

{
   "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"
}

O servidor aceitará os dados do formulário neste formato convertido.

Aqui está a função:

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;
}

Invocação:

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

Estou usando o testJSON apenas para ver os resultados convertidos, pois não seria capaz de extrair o conteúdo de form_data. Pós-chamada AJAX:

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

Oi, Raj, que tal arrays? Digamos que você tenha mais de 1 endereço de cobrança. Como você consertaria isso?
Sam

1
Eu encontrei a resposta! Sua postagem é muito útil. Obrigado!
Sam

3

Desculpe pela resposta tardia, mas eu estava lutando com isso porque o Angular 2 atualmente não suporta o upload de arquivos. Então, a maneira de fazer isso foi enviando um XMLHttpRequestcom FormData. Então, criei uma função para fazer isso. Estou usando o Typescript . Para convertê-lo para Javascript, basta remover a declaração de tipos de dados.

/**
     * 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);
        }
    }

Você deve incluir índices de matriz em vez de []propriedades de objeto dentro de uma matriz numérica para permanecer intacta
Vicary

1

Versão TypeScript:

static convertModelToFormData(model: any, form: FormData = null, namespace = ''): FormData {
    let formData = form || new FormData();
    for (let propertyName in model) {
      if (!model.hasOwnProperty(propertyName) || model[propertyName] == undefined) continue;
      let formKey = namespace ? `${namespace}[${propertyName}]` : propertyName;
      if (model[propertyName] instanceof Date) {        
        formData.append(formKey, this.dateTimeToString(model[propertyName]));
      }
      else if (model[propertyName] instanceof Array) {
        model[propertyName].forEach((element, index) => {
          if (typeof element != 'object')
            formData.append(`${formKey}[]`, element);
          else {
            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;
  }

https://gist.github.com/Mds92/091828ea857cc556db2ca0f991fee9f6


1
Em primeiro lugar, namespaceé uma palavra-chave reservada em TypeScript( typescriptlang.org/docs/handbook/namespaces.html e github.com/Microsoft/TypeScript/issues/… ). Além disso, parece que você se esqueceu de tratar Filedesde a última elsevontade append "[object File]"até o formData.
Jyrkka

1

Você pode simplesmente instalar qs:

npm i qs

Basta importar:

import qs from 'qs'

Passe o objeto para qs.stringify():

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

qs.stringify(item)

1

Recursivamente

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>


melhor resposta imho
ling

0

Este método converte um objeto JS em 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());
}


Basta corrigir a chamada de entradas de objetos aninhados de iteração: Object.entries(value).forEach((v, k) => acc.append(`${key}[${v[0]}]`, v[1]));
heber gentilin

0

No meu caso, meu objeto também tinha uma propriedade que era array de arquivos. Como são binários, devem ser tratados de forma diferente - o índice não precisa fazer parte da chave. Então eu modifiquei a resposta de @Vladimir Novopashin e @ 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;
}

0

Eu usei isso para postar meus dados de objeto como dados de formulário.

const encodeData = require('querystring');

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

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

0

Talvez você esteja procurando por isso, um código que recebe seu objeto javascript, cria um objeto FormData a partir dele e, em seguida, faz o POST em seu servidor usando a nova API Fetch :

    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);
        });

0

Eu me refiro a isso da resposta de Gudradain . Eu edito um pouco no formato 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();

0

Posso chegar um pouco atrasado para a festa, mas foi isso que criei para converter um objeto singular em 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;
}

Como funciona? Ele irá converter e retornar todas as propriedades esperadas dos objetos File que você definiu na lista de ignorados (segundo argumento. Se alguém pudesse me dizer uma maneira melhor de determinar isso, isso ajudaria!) Em uma string json usando JSON.stringify. Em seguida, em seu servidor, você só precisará convertê-lo de volta em um objeto JSON.

Exemplo:

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');
  })
}

Ainda sou meio novo em solicitações de Http e JavaScript, então qualquer feedback seria muito apreciado!


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.