如何将FormData(HTML5对象)转换为JSON

245

如何将HTML5的FormData对象中的条目转换为JSON格式?

解决方案不应使用jQuery。此外,它不应仅仅序列化整个FormData对象,而是仅包含其键/值条目。


2
你想要做什么?JSON.stringify()有帮助吗?也许你可以尝试其他方法来解决问题? - Justinas
8
不是重复,因为我不想将JavaScript对象转换为JSON,也不想使用Jquery.serialize()。 - Leonardo Villela
请查看此链接:https://dev59.com/f3M_5IYBdhLWcg3w4nfb#39248551 - Bhavik Hirani
可用于嵌套表单:https://dev59.com/9mwMtIcB2Jgan1zn6j0K#70057955 - T.Todua
很多事情取决于你追求的目标。如果你想通过XHR请求发送JSON,你不需要进行转换,因为这会导致多个值的问题。你可以使用URLSearchParams API,例如:let data = new URLSearchParams(formData) - dlnsk
34个回答

329

您也可以直接在FormData对象上使用forEach

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

更新:

对于那些喜欢使用ES6箭头函数的人,以下是相同解决方案:

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

更新 2:

对于那些想要支持多选列表或其他具有多个值的表单元素的人们(由于下面的答案有很多关于此问题的评论,我将添加一个可能的解决方案):

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

这里有一个Fiddle演示了使用这种方法来处理简单的多选列表。

更新 3:

对于那些最终目的是将表单数据转换为 JSON 并通过XMLHttpRequest发送到服务器的人,你可以直接发送 FormData 对象,而不必进行转换。代码如下:

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

参考MDN上的Using FormData Objects文档。

或者你也可以使用现代化的Fetch API来实现相同的功能:

fetch('http://example.com/submitform.php', {
  method: 'POST',
  body: formData
}).then((response) => { 
  // do something with response here... 
});

请参阅MDN上的使用 Fetch API了解更多相关信息。

更新4:

正如我在回答下面的评论中提到的,JSON中的stringify方法并不支持所有类型的对象。关于支持哪些类型的信息,请参考MDN文档中的描述部分

描述中还提到:

如果值具有toJSON()方法,则它负责定义将序列化哪些数据。

这意味着您可以提供自己的toJSON序列化方法,并编写用于序列化自定义对象的逻辑。通过这种方式,您可以快速轻松地为更复杂的对象树构建序列化支持。


1
如@TomasPrado的回答所述,请确保您不需要支持IE11。 - Wilt
7
由于多选表单元素共享相同的键,因此此方法无法正常工作。您最终会覆盖该键对应的值,只返回最后选择的一个元素的值。 - Sean
1
@Sean,我提供了一个适用于<SELECT MULTIPLE><INPUT type="checkbox">具有相同名称的多个值的答案,通过将值转换为数组来实现。 - some
如果您正在使用fetch,请确保使用正确的标题与application/json - Pro Q
15
除非需要多选等功能,否则使用JSON.stringify(Object.fromEntries(formData));的答案会更好。 - Tom Stickel
显示剩余12条评论

315

在2019年,这种任务变得非常容易。

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

Object.fromEntries:Chrome 73+、Firefox 63+和Safari 12.1以上版本支持。

如评论中所述,请注意:FormData 可以包含具有相同键的多个值(例如,名称相同的复选框)。Object.fromEntries() 会丢弃重复值并只保留最后一个值


5
我来发表这个帖子,喜欢看这个主题,看到答案在这些年里如何演变。 - Marcin
36
在具有相同名称的多个字段的表单上,似乎无法正常工作。 - JukkaP
9
现在是2020年,此代码无法处理<select multiple>或<input type="checkbox">的多个选定值。 - some
7
建议使用formData.entries:JSON.stringify(Object.fromEntries(formData.entries())); - Kohver
12
@Kohver,更好在哪里?它会删除重复项吗?它解决了什么样的问题,使得它更好? - CervEd
显示剩余3条评论

32

以下是一种更加函数式的方法,不需要使用任何库。

Array.from(formData.entries()).reduce((memo, [key, value]) => ({
  ...memo,
  [key]: value,
}), {});

示例:

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

  const formData = new FormData(e.target);
  const data = Array.from(formData.entries()).reduce((memo, [key, value]) => ({
    ...memo,
    [key]: value,
  }), {});
  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>


7
我很喜欢这个答案,但仍无法处理多个项目。我根据这个答案发表了一个新的答案来处理这些情况。 - CarlosH.
1
我建议用[key, value]替换pair,以利用解构并提高可读性。 - Christian Vincenzo Traina

16

到目前为止,我还没有看到有关FormData.getAll方法的任何提及。

除了从FormData对象中返回与给定密钥相关联的所有值之外,使用Object.fromEntries方法真的非常简单,正如其他人在这里指定的那样。

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)
  ])
)

代码片段演示

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>


15
如果您有多个名称相同的条目,例如如果使用<SELECT multiple>或具有相同名称的多个<INPUT type="checkbox">,则需要注意并创建值数组。否则,您只会得到最后一个选择的值。
以下是现代ES6变体:
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 );
}

稍微陈旧的代码(但仍未得到 IE11 的支持,因为它不支持在 FormData 上使用 ForEachentries

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

1
但是当只有一个复选框被选中时,该值仍然是单个字符串,而不是数组。 - ArtixZ
这是正确的。由于您可以拥有多种类型的具有相同名称的字段,因此我决定设置第一次出现的值,并仅在发现多个值时将其转换为数组。我不想先扫描表单以查找可能导致数组的字段名称。 - some

13

如果你需要支持将嵌套字段序列化,类似于PHP处理表单字段的方式,你可以使用以下函数

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>


1
需要将“function update(data, keys, value) {”中的数组“[]”的默认值更新为对象“{}”,以解决空数组问题。 - Chintan Mathukiya
我建议在reduce之前添加一个数组过滤器,以从最终对象中删除空字段。 - Emaia
@ChintanMathukiya Maia,你能分享一下你遇到意外输出的样本输入吗? - Joyce Babu
@Ednilson,您能分享一下您看到空数组输出的示例数据吗? - Joyce Babu

9
您可以通过使用FormData()对象来实现此目的。该FormData对象将使用每个元素的名称属性作为键,并使用它们提交的值作为值来填充表单的当前键/值。它还将对文件输入内容进行编码。
示例:
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);

});

这不会生成json。 - Liam
1
@Liam,你尝试过在表单元素中使用这个吗?并告诉我为什么它不能生成JSON对象? - GiriB
1
没有所谓的 JSON 对象。JSON 是一种字符串表示法。 - Liam
1
@Liam 在对象创建后,他们可以使用JSON.stringify(result)。我已经编辑了我的答案,请检查一下。请撤回你的反对票。 - GiriB
1
如果你使用ES6,你也可以使for of语句更加表达性强:for (const [key, value] of formData.entries()) - Teddy Zetterlund
显示剩余2条评论

7

这篇文章已经有一年了...但是,我真的非常喜欢ES6 @dzuc的答案。然而,它并不能处理多个选择框或复选框。这已经被指出,并提供了代码解决方案。我发现它们很笨重,也不够优化。所以我写了两个版本基于@dzuc来处理这些情况:

  • 对于ASP样式表单,其中多个项目名称可以简单地重复。
let r=Array.from(fd).reduce(
  (o , [k,v]) => (
     (!o[k])
     ? {...o , [k] : v}
     : {...o , [k] : [...o[k] , v]}
   )
   ,{}
);
let obj=JSON.stringify(r);

一行代码版 Hotshot 版本:
Array.from(fd).reduce((o,[k,v])=>((!o[k])?{...o,[k]:v}:{...o,[k]:[...o[k],v]}),{});
  • 对于PHP样式的表单,多个项目名称必须具有[]后缀。
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);

一行热门版本:

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}),{});
  • 支持多级数组的 PHP 表单扩展。

自上次编写上一个案例以来,在工作中遇到了具有多层复选框的 PHP 表单。我编写了一个新案例来支持先前的案例和这个案例。我创建了一个代码片段来更好地展示这个案例,结果显示在控制台上供演示使用,根据您的需求进行修改。尽我所能优化它而不影响性能,但是它会影响一些人的可读性。它利用了数组是对象且指向数组的变量保留为引用的特点。对于这个案例我并没有什么高招,随意修改。

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>


2
Array.from(fd).reduce((obj, [k, v]) => ({...obj, [k]: v}), {}); 炫酷版 es2018 - nackjicholson
1
@nackjicholson 是的,你说得对,省略 .entries() 和 [k,v] 元素可以简化代码。我会重写代码以包含这些改进。但是你的代码仍然会在重复值上进行覆盖。 - CarlosH.
15
我能理解你的努力,但像这样的代码是荒谬的。没有人想看到用字母表示变量和对象,这不是1985年了。 - Tom Stickel
2
很棒的解决方案,不要听上面那个讨厌的人的话。 - Mike

6

易于使用的功能

我已经为此创建了一个功能函数

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

    return ConvertedJSON
}

使用示例

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

在这段代码中,我使用了一个for循环创建了一个空的JSON变量,在每次迭代中,我将formData对象中的key作为JSON键。你可以在我的GitHub JS库中找到此代码,如果需要改进,请给我建议,我已经将代码放在这里:https://github.com/alijamal14/Utilities/blob/master/Utilities.js

1
这不能处理<select multiple><input type="checkbox">的多个选定值。 - some

5
这里有一个函数,可以将 formData 对象转换为 JSON 字符串。
  • 适用于多个输入项和嵌套数组。
  • 适用于数字和命名的输入名称数组。

例如,您可以拥有以下表单字段:

<select name="select[]" multiple></select>
<input name="check[a][0][]" type="checkbox" value="test"/>

使用方法:

let json = form2json(formData);

函数:
function form2json(data) {
        
        let method = function (object,pair) {
            
            let keys = pair[0].replace(/\]/g,'').split('[');
            let key = keys[0];
            let value = pair[1];
            
            if (keys.length > 1) {
                
                let i,x,segment;
                let last = value;
                let type = isNaN(keys[1]) ? {} : [];
                
                value = segment = object[key] || type;
                
                for (i = 1; i < keys.length; i++) {
                    
                    x = keys[i];
                    
                    if (i == keys.length-1) {
                        if (Array.isArray(segment)) {
                            segment.push(last);
                        } else {
                            segment[x] = last;
                        }
                    } else if (segment[x] == undefined) {
                        segment[x] = isNaN(keys[i+1]) ? {} : [];
                    }
                    
                    segment = segment[x];
                    
                }
                
            }
            
            object[key] = value;
            
            return object;
            
        }
        
        let object = Array.from(data).reduce(method,{});
        
        return JSON.stringify(object);
        
    }

1
这对我来说似乎是最好的答案,并涵盖了各种可能的输入名称。感谢分享。(只需在第一条语句前添加“function”,你就可以了。) - z-x
函数已经添加,谢谢! - frankichiro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接