Como converter FormData (objeto HTML5) para JSON

104

Como converter o objeto FormData HTML5 para JSON? Sem Jquery e manipulando propriedades aninhadas em FormData como um objeto.

Leonardo Villela
fonte
2
O que você está tentando fazer? Será que JSON.stringify()ajuda? Talvez você tente consertar algo que pode ser feito de outra maneira?
Justinas
Possível duplicata de Converter objeto JS em string JSON
Liam
3
Não é duplicado, pois não quero converter um objeto javascript em json, nem quero usar Jquery.serialize ()
Leonardo Villela
verifique isto: stackoverflow.com/a/39248551/6293856
Bhavik Hirani

Respostas:

145

Você também pode usar forEachno FormDataobjeto diretamente:

var object = {};
formData.forEach(function(value, key){
    object[key] = value;
});
var json = JSON.stringify(object);

ATUALIZAR:

E para quem prefere a mesma solução com as funções de seta ES6 :

var object = {};
formData.forEach((value, key) => {object[key] = value});
var json = JSON.stringify(object);

ATUALIZAÇÃO 2:

E para aqueles que desejam suporte para listas de seleção múltipla ou outros elementos de formulário com valores múltiplos (já que existem tantos comentários abaixo da resposta a respeito desse problema, adicionarei uma possível solução) :

var object = {};
formData.forEach((value, key) => {
    // Reflect.has in favor of: object.hasOwnProperty(key)
    if(!Reflect.has(object, key)){
        object[key] = value;
        return;
    }
    if(!Array.isArray(object[key])){
        object[key] = [object[key]];    
    }
    object[key].push(value);
});
var json = JSON.stringify(object);

Aqui está um Fiddle demonstrando o uso desse método com uma lista de seleção múltipla simples.

ATUALIZAÇÃO 3:

Como uma nota lateral para quem vai parar aqui, caso o objetivo de converter os dados do formulário para json seja enviá-los por meio de uma solicitação HTTP XML para um servidor, você pode enviar o FormDataobjeto diretamente sem convertê-lo. Simples assim:

var request = new XMLHttpRequest();
request.open("POST", "http://example.com/submitform.php");
request.send(formData);

Consulte também Usando objetos FormData no MDN para referência :

ATUALIZAÇÃO 4:

Conforme mencionado em um dos comentários abaixo, minha resposta o stringifymétodo JSON não funcionará fora da caixa para todos os tipos de objetos. Para obter mais informações sobre quais tipos são suportados, gostaria de consultar a seção Descrição na documentação do MDN deJSON.stringify .

Na descrição também é mencionado que:

Se o valor possuir um método toJSON (), ele é responsável por definir quais dados serão serializados.

Isso significa que você pode fornecer seu próprio toJSONmétodo de serialização com lógica para serializar seus objetos personalizados. Assim, você pode construir suporte de serialização de forma rápida e fácil para árvores de objetos mais complexas.

Wilt
fonte
1
Conforme mencionado na resposta de @TomasPrado, certifique-se de não precisar de suporte para o IE11.
Wilt
4
Isso não funciona com elementos de formulário de seleção múltipla, pois eles compartilham a mesma chave, você acaba substituindo os valores, retornando apenas o último elemento selecionado
Sean
1
@Sean Eu dei uma resposta que funciona com vários valores para <SELECT MULTIPLE>e <INPUT type="checkbox">com o mesmo nome, convertendo o valor em um array.
cerca de
formData não é uma série. como você pode iterar? aceitou a resposta, mas algo falhou.
Nuri YILMAZ
4
A menos que haja necessidade de seleções múltiplas, etc., a resposta com JSON.stringify(Object.fromEntries(formData));é muito mais agradável
Tom Stickel
108

Em 2019, esse tipo de tarefa ficou super fácil.

JSON.stringify(Object.fromEntries(formData));

Object.fromEntries: Compatível com Chrome 73+, Firefox 63+, Safari 12.1

hakatashi
fonte
2
Vim aqui para postar isso, adoro olhar este tópico e ver como as respostas têm evoluído ao longo dos anos
Marcin
13
Isso não parece funcionar corretamente em formulários que possuem vários campos com o mesmo nome.
JukkaP
3
É 2020 e não lida com vários valores selecionados de <select multiple>ou <input type="checkbox"> 😞
alguns de
4
Melhor usar formData.entries:JSON.stringify(Object.fromEntries(formData.entries()));
Kohver
22

Esta é uma maneira de fazer isso em um estilo mais funcional, sem o uso de uma biblioteca.

Array.from(formData.entries()).reduce((memo, pair) => ({
  ...memo,
  [pair[0]]: pair[1],
}), {});

Exemplo:

document.getElementById('foobar').addEventListener('submit', (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);
  const data = Array.from(formData.entries()).reduce((memo, pair) => ({
    ...memo,
    [pair[0]]: pair[1],
  }), {});
  document.getElementById('output').innerHTML = JSON.stringify(data);
});
<form id='foobar'>
  <input name='baz' />
  <input type='submit' />
</form>

<pre id='output'>Input some value and submit</pre>

dzuc
fonte
1
Melhor resposta aqui. Obrigado :)
Chunky Chunk
4
Eu realmente gosto dessa resposta, mas ainda não lido com vários itens. Publiquei uma nova resposta com base nesta para lidar com esses casos.
CarlosH.
9

Se você tiver várias entradas com o mesmo nome, por exemplo, se você usar <SELECT multiple>ou tiver várias <INPUT type="checkbox">com o mesmo nome, você precisa cuidar disso e fazer uma matriz do valor. Caso contrário, você obtém apenas o último valor selecionado.

Aqui está a variante ES6 moderna:

function formToJSON( elem ) {
  let output = {};
  new FormData( elem ).forEach(
    ( value, key ) => {
      // Check if property already exist
      if ( Object.prototype.hasOwnProperty.call( output, key ) ) {
        let current = output[ key ];
        if ( !Array.isArray( current ) ) {
          // If it's not an array, convert it to an array.
          current = output[ key ] = [ current ];
        }
        current.push( value ); // Add the new value to the array.
      } else {
        output[ key ] = value;
      }
    }
  );
  return JSON.stringify( output );
}

Código ligeiramente mais velho (mas ainda não é suportado pelo IE11, uma vez que não suporta ForEachou entrieson FormData)

function formToJSON( elem ) {
  var current, entries, item, key, output, value;
  output = {};
  entries = new FormData( elem ).entries();
  // Iterate over values, and assign to item.
  while ( item = entries.next().value )
    {
      // assign to variables to make the code more readable.
      key = item[0];
      value = item[1];
      // Check if key already exist
      if (Object.prototype.hasOwnProperty.call( output, key)) {
        current = output[ key ];
        if ( !Array.isArray( current ) ) {
          // If it's not an array, convert it to an array.
          current = output[ key ] = [ current ];
        }
        current.push( value ); // Add the new value to the array.
      } else {
        output[ key ] = value;
      }
    }
    return JSON.stringify( output );
  }
alguns
fonte
7

Você pode fazer isso usando o objeto FormData () . Este objeto FormData será preenchido com as chaves / valores atuais do formulário usando a propriedade name de cada elemento para as chaves e seu valor enviado para os valores. Ele também codificará o conteúdo de entrada do arquivo.

Exemplo:

var myForm = document.getElementById('myForm');
myForm.addEventListener('submit', function(event)
{
    event.preventDefault();
    var formData = new FormData(myForm),
        result = {};

    for (var entry of formData.entries())
    {
        result[entry[0]] = entry[1];
    }
    result = JSON.stringify(result)
    console.log(result);

});
GiriB
fonte
Isso não produz json
Liam
1
@Liam Você já tentou fazer isso com elementos de formulário? E deixe-me saber por que ele não produz objeto JSON?
GiriB
1
Não existe um objeto json. Json é uma notação de string
Liam
1
@Liam Após a criação do objeto, eles podem usar JSON.stringify (resultado). E eu editei minha resposta. Por favor verifique isto. E retirar o voto negativo.
GiriB
1
Você também pode tornar a afirmação for de mais expressiva se estiver usando ES6: for (const [key, value] of formData.entries())
Teddy Zetterlund
7

Função fácil de usar

Eu criei uma função para isso

function FormDataToJSON(FormElement){    
    var formData = new FormData(FormElement);
    var ConvertedJSON= {};
    for (const [key, value]  of formData.entries())
    {
        ConvertedJSON[key] = value;
    }

    return ConvertedJSON
}

Exemplo de uso

var ReceivedJSON = FormDataToJSON(document.getElementById('FormId');)

Neste código, criei uma variável JSON vazia usando o forloop. Usei keys do objeto formData para Chaves JSON em cada Itração.

Você encontra este código na minha biblioteca JS no GitHub Sugira-me se precisar de melhorias Coloquei o código aqui https://github.com/alijamal14/Utilities/blob/master/Utilities.js

Ali Jamal
fonte
1
@zuluk explicou: Obrigado
Ali Jamal,
Isso não lida com vários valores selecionados de <select multiple>ou <input type="checkbox">.
cerca de
5

Este post já tem um ano ... mas, realmente, gostei muito da resposta do ES6 @dzuc. No entanto, está incompleto por não ser capaz de lidar com várias seleções ou caixas de seleção. Isso já apontou e soluções de código foram oferecidas. Acho que são pesados ​​e não otimizados. Então, escrevi 2 versões baseadas em @dzuc para lidar com esses casos:

  • Para formulários de estilo ASP em que o nome de vários itens pode ser simplesmente repetido.
let r=Array.from(fd).reduce(
  (o , [k,v]) => (
     (!o[k])
     ? {...o , [k] : v}
     : {...o , [k] : [...o[k] , v]}
   )
   ,{}
);
let obj=JSON.stringify(r);

Versão Hotshot de uma linha:

Array.from(fd).reduce((o,[k,v])=>((!o[k])?{...o,[k]:v}:{...o,[k]:[...o[k],v]}),{});
  • Para formulários de estilo PHP em que os nomes de vários itens devem ter um []sufixo.
let r=Array.from(fd).reduce(
  (o , [k,v]) => (
    (k.split('[').length>1)
    ? (k=k.split('[')[0]
      , (!o[k])
      ? {...o , [k] : [v]}
      : {...o , [k] : [...o[k] , v ]}
    )
    : {...o , [k] : v}
  )
  ,{}
);
let obj=JSON.stringify(r);

Versão Hotshot de uma linha:

Array.from(fd).reduce((o,[k,v])=>((k.split('[').length>1)?(k=k.split('[')[0],(!o[k])?{...o,[k]:[v]}:{...o,[k]:[...o[k],v]}):{...o,[k]:v}),{});
  • Extensão do formulário PHP que suporta matrizes de vários níveis.

Desde a última vez que escrevi o segundo caso anterior, no trabalho surgiu um caso em que o formulário do PHP tinha caixas de seleção em vários níveis. Eu escrevi um novo caso para apoiar o caso anterior e este. Criei um trecho para melhor mostrar este caso, o resultado mostrado no console para este demo, modifique isso de acordo com sua necessidade. Tentei otimizá-lo o melhor que pude sem comprometer o desempenho, no entanto, comprometeu alguma legibilidade humana. Ele aproveita que os arrays são objetos e as variáveis ​​que apontam para os arrays são mantidas como referência. Nenhum figurão para este, fique à vontade.

let nosubmit = (e) => {
  e.preventDefault();
  const f = Array.from(new FormData(e.target));
  const obj = f.reduce((o, [k, v]) => {
    let a = v,
      b, i,
      m = k.split('['),
      n = m[0],
      l = m.length;
    if (l > 1) {
      a = b = o[n] || [];
      for (i = 1; i < l; i++) {
        m[i] = (m[i].split(']')[0] || b.length) * 1;
        b = b[m[i]] = ((i + 1) == l) ? v : b[m[i]] || [];
      }
    }
    return { ...o, [n]: a };
  }, {});
  console.log(obj);
}
document.querySelector('#theform').addEventListener('submit', nosubmit, {capture: true});
<h1>Multilevel Form</h1>
<form action="#" method="POST" enctype="multipart/form-data" id="theform">
  <input type="hidden" name="_id" value="93242" />
  <input type="hidden" name="_fid" value="45c0ec96929bc0d39a904ab5c7af70ef" />
  <label>Select:
    <select name="uselect">
      <option value="A">A</option>
      <option value="B">B</option>
      <option value="C">C</option>
    </select>
  </label>
  <br /><br />
  <label>Checkboxes one level:<br/>
    <input name="c1[]" type="checkbox" checked value="1"/>v1 
    <input name="c1[]" type="checkbox" checked value="2"/>v2
    <input name="c1[]" type="checkbox" checked value="3"/>v3
  </label>
  <br /><br />
  <label>Checkboxes two levels:<br/>
    <input name="c2[0][]" type="checkbox" checked value="4"/>0 v4 
    <input name="c2[0][]" type="checkbox" checked value="5"/>0 v5
    <input name="c2[0][]" type="checkbox" checked value="6"/>0 v6
    <br/>
    <input name="c2[1][]" type="checkbox" checked value="7"/>1 v7 
    <input name="c2[1][]" type="checkbox" checked value="8"/>1 v8
    <input name="c2[1][]" type="checkbox" checked value="9"/>1 v9
  </label>
  <br /><br />
  <label>Radios:
    <input type="radio" name="uradio" value="yes">YES
    <input type="radio" name="uradio" checked value="no">NO
  </label>
  <br /><br />
  <input type="submit" value="Submit" />
</form>

CarlosH.
fonte
2
Array.from(fd).reduce((obj, [k, v]) => ({...obj, [k]: v}), {});versão principal es2018
nackjicholson
1
@nackjicholson Sim, você está certo, omitir .entries () e o elemento [k, v] simplifica o código. Vou reescrever o código para incluir essas melhorias. No entanto, o seu ainda sobrescreverá em valores repetidos.
CarlosH.
3
Embora eu agradeça o esforço - um código como esse é absurdo. Ninguém quer olhar as letras para variáveis ​​e objetos, não estamos em 1985.
Tom Stickel
4

O método FormData .entriese a for ofexpressão não são suportados no IE11 e Safari.

Aqui está uma versão mais simples para suportar Safari, Chrome, Firefox e Edge

function formDataToJSON(formElement) {    
    var formData = new FormData(formElement),
        convertedJSON = {};

    formData.forEach(function(value, key) { 
        convertedJSON[key] = value;
    });

    return convertedJSON;
}

Aviso: esta resposta não funciona no IE11.
FormData não tem um forEachmétodo no IE11.
Ainda estou procurando uma solução final para oferecer suporte a todos os principais navegadores.

Tomas Prado
fonte
isto é perfeito! temos que oferecer suporte a navegadores mais antigos e o uso do iterador não é muito intuitivo.
Peter Hawkins
3

Se você estiver usando lodash, isso pode ser feito de forma concisa com fromPairs

import {fromPairs} from 'lodash';

const object = fromPairs(Array.from(formData.entries()));
Erik van Velzen
fonte
3

Se você precisar de suporte para serializar campos aninhados, semelhante a como o PHP lida com campos de formulário, você pode usar a seguinte função

function update(data, keys, value) {
  if (keys.length === 0) {
    // Leaf node
    return value;
  }

  let key = keys.shift();
  if (!key) {
    data = data || [];
    if (Array.isArray(data)) {
      key = data.length;
    }
  }

  // Try converting key to a numeric value
  let index = +key;
  if (!isNaN(index)) {
    // We have a numeric index, make data a numeric array
    // This will not work if this is a associative array 
    // with numeric keys
    data = data || [];
    key = index;
  }
  
  // If none of the above matched, we have an associative array
  data = data || {};

  let val = update(data[key], keys, value);
  data[key] = val;

  return data;
}

function serializeForm(form) {
  return Array.from((new FormData(form)).entries())
    .reduce((data, [field, value]) => {
      let [_, prefix, keys] = field.match(/^([^\[]+)((?:\[[^\]]*\])*)/);

      if (keys) {
        keys = Array.from(keys.matchAll(/\[([^\]]*)\]/g), m => m[1]);
        value = update(data[prefix], keys, value);
      }
      data[prefix] = value;
      return data;
    }, {});
}

document.getElementById('output').textContent = JSON.stringify(serializeForm(document.getElementById('form')), null, 2);
<form id="form">
  <input name="field1" value="Field 1">
  <input name="field2[]" value="Field 21">
  <input name="field2[]" value="Field 22">
  <input name="field3[a]" value="Field 3a">
  <input name="field3[b]" value="Field 3b">
  <input name="field3[c]" value="Field 3c">
  <input name="field4[x][a]" value="Field xa">
  <input name="field4[x][b]" value="Field xb">
  <input name="field4[x][c]" value="Field xc">
  <input name="field4[y][a]" value="Field ya">
  <input name="field5[z][0]" value="Field z0">
  <input name="field5[z][]" value="Field z1">
  <input name="field6.z" value="Field 6Z0">
  <input name="field6.z" value="Field 6Z1">
</form>

<h2>Output</h2>
<pre id="output">
</pre>

Joyce Babu
fonte
1
É necessário atualizar o valor padrão de "dados" da matriz "[]" para o objeto "{}" em "atualização de função (dados, chaves, valor) {" remove o problema de matriz em branco.
Chintan Mathukiya
Eu também sugiro adicionar um filtro de array antes de reduzir para remover campos vazios do objeto final
Ednilson Maia
@ChintanMathukiya Maia Você pode compartilhar o exemplo de entrada para a qual está tendo uma saída inesperada?
Joyce Babu,
@Ednilson Você pode compartilhar os dados de amostra para os quais está vendo uma saída de array vazia?
Joyce Babu,
2

Você pode tentar isso

formDataToJSON($('#form_example'));

# Create a function to convert the serialize and convert the form data
# to JSON
# @param : $('#form_example');
# @return a JSON Stringify
function formDataToJSON(form) {
    let obj = {};
    let formData = form.serialize();
    let formArray = formData.split("&");

    for (inputData of formArray){
        let dataTmp = inputData.split('=');
        obj[dataTmp[0]] = dataTmp[1];
    }
    return JSON.stringify(obj);
}
Ivan Fretes
fonte
2

Mesmo que a resposta de @dzuc já seja muito boa, você pode usar a desestruturação de array (disponível em navegadores modernos ou com o Babel) para torná-la ainda mais elegante:

// original version from @dzuc
const data = Array.from(formData.entries())
  .reduce((memo, pair) => ({
    ...memo,
    [pair[0]: pair[1],
  }), {})

// with array destructuring
const data = Array.from(formData.entries())
  .reduce((memo,[key, value]) => ({
    ...memo,
    [key]: value,
  }), {})
Jeremias Erbs
fonte
2

One-liner abusivo!

Array.from(fd).reduce((obj, [k, v]) => ({...obj, [k]: v}), {});

Hoje eu aprendi que o firefox tem suporte a propagação de objetos e desestruturação de array!

Nackjicholson
fonte
1

Se os itens a seguir atendem às suas necessidades, você está com sorte:

  1. Você deseja converter um array de arrays como [['key','value1'], ['key2','value2'](como o que FormData oferece) em um objeto chave-> valor como {key1: 'value1', key2: 'value2'}e convertê-lo em uma string JSON.
  2. Você está visando navegadores / dispositivos com o interpretador ES6 mais recente ou está compilando com algo como o babel.
  3. Você quer a maneira mais ínfima de conseguir isso.

Aqui está o código de que você precisa:

const data = new FormData(document.querySelector('form'));
const json = JSON.stringify(Array.from(data).reduce((o,[k,v])=>(o[k]=v,o),{}));

Espero que isso ajude alguém.

KyleFarris
fonte
1

Não vi menções ao método FormData.getAll até agora.

Além de retornar todos os valores associados a uma determinada chave de dentro de um objeto FormData, torna-se muito simples usar o método Object.fromEntries conforme especificado por outros aqui.

var formData = new FormData(document.forms[0])

var obj = Object.fromEntries(
  Array.from(formData.keys()).map(key => [
    key, formData.getAll(key).length > 1 
    ? formData.getAll(key)
    : formData.get(key)
  ])
)

Snippet em ação

var formData = new FormData(document.forms[0])

var obj = Object.fromEntries(Array.from(formData.keys()).map(key => [key, formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key)]))

document.write(`<pre>${JSON.stringify(obj)}</pre>`)
<form action="#">
  <input name="name" value="Robinson" />
  <input name="items" value="Vin" />
  <input name="items" value="Fromage" />
  <select name="animals" multiple id="animals">
    <option value="tiger" selected>Tigre</option>
    <option value="turtle" selected>Tortue</option>
    <option value="monkey">Singe</option>
  </select>
</form>

OpSocket
fonte
0

Funcionou para mim

                var myForm = document.getElementById("form");
                var formData = new FormData(myForm),
                obj = {};
                for (var entry of formData.entries()){
                    obj[entry[0]] = entry[1];
                }
                console.log(obj);
Shahid Hussain Abbasi
fonte
Não vai lidar com vários valores selecionados de <select multiple>ou<input type="checkbox">
alguns de
0

No meu caso, os dados eram dados, a base de incêndio esperava um objeto, mas os dados contêm o objeto, bem como todos os outros materiais, então tentei data.value funcionou !!!

Rahul Solanki
fonte
0

Estou chegando tarde aqui. No entanto, fiz um método simples que verifica a entrada type = "caixa de seleção"

var formData = new FormData($form.get(0));
        var objectData = {};
        formData.forEach(function (value, key) {
            var updatedValue = value;
            if ($('input[name="' + key + '"]').attr("type") === "checkbox" && $('input[name="' + key + '"]').is(":checked")) {
                updatedValue = true; // we don't set false due to it is by default on HTML
            }
            objectData[key] = updatedValue;
        });
var jsonData = JSON.stringify(objectData);

Espero que isso ajude mais alguém.

acido
fonte
-1

Você pode fazer esse trabalho facilmente, sem usar nada especial. Um código como o abaixo será suficiente.

var form = $(e.currentTarget);

var formData = objectifyForm(form);


function objectifyForm(formArray) {

    var returnArray = {};
    for (var i = 0; i < formArray[0].length; i++) {
        var name = formArray[0][i]['name'];
        if (name == "") continue;
        if (formArray[0][i]['type'] == "hidden" && returnArray[name] != undefined) continue;
        if ($(formArray[0][i]).attr("type") == "radio") {
            var radioInputs = $("[name='" + name + "']");
            var value = null;
            radioInputs.each(function (radio) {
                if ($(this)[0].checked == true) {
                    value = $(this).attr("id").split("_")[$(this).attr("id").split("_").length - 1];
                }
            });
            returnArray[name] = value;
        }
        else if ($(formArray[0][i]).attr("type") == "checkbox") {
            returnArray[name] = $(formArray[0][i])[0].checked;
        }
        else
            returnArray[name] = formArray[0][i]['value'];
    }



    return returnArray;
};
Nercan
fonte