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?

Kamran Ahmed
fonte
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

adeneo
fonte
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());
Jacob Lauritzen
fonte
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);
Vladimir Novopashin
fonte
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;
}
Gudradain
fonte
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 );
Udayraj Khuman
fonte
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) {
            }
        });
Raj Pawan Gumdal
fonte
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);
        }
    }
Aleksandrus
fonte
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

Mohammad Dayyan
fonte
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)
Balaj Khan
fonte
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>

vmartins
fonte
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());
}

Monge Macaco
fonte
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;
}
Elnoor
fonte
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));
Yunus ER
fonte
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);
        });
Oswaldo Rodriguez Gonzalez
fonte
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();
Mikhael Pramodana
fonte
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!

Gibbu
fonte
-6

Tente 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]]
Manioz
fonte