我如何将条目从HTML5 FormData对象转换为JSON?

解决方案不应该使用jQuery。而且,它不应该简单地序列化整个FormData对象,而应该只序列化它的键/值条目。


当前回答

编辑:我看到已经有一个答案得出了非常相似的结果。

两个主要区别:

这不会将数值键作为数组下标,而是将它们视为对象键。可能不是正确的行为,但我个人从来没有写过使用这种符号的表单,可能会更新它。 这不是一个递归函数,这显然不是一个有利或不利的问题

忽略任何缺乏效率的问题。这将处理无限嵌套和数组的重复键。这显然不会转换文件之类的东西,但我不需要它,所以我没有添加它。

下面是一个JSFiddle的转换示例,可以用这个函数实现

一个很好的使用示例(这也是我的用例)是从html表单元素创建一个新的FormData对象,并轻松地将其转换为JSON进行发送。

/**
 * @param {FormData} formData
 * @return {Object}
 */
function formDataToObject(formData) {
    const object = {};
    for (let pair of formData.entries()) {
        const key = pair[0];
        const value = pair[1];
        const isArray = key.endsWith('[]');
        const name = key.substring(0, key.length - (2 * isArray));
        const path = name.replaceAll(']', '');
        const pathParts = path.split('[');
        const partialsCount = pathParts.length;
        let iterationObject = object;
        for (let i = 0; i < partialsCount; i++) {
            let part = pathParts[i];
            let iterationObjectElement = iterationObject[part];
            if (i !== partialsCount - 1) {
                if (!iterationObject.hasOwnProperty(part) || typeof iterationObjectElement !== "object") {
                    iterationObject[part] = {};
                }
                iterationObject = iterationObject[part];
            } else {
                if (isArray) {
                    if (!iterationObject.hasOwnProperty(part)) {
                        iterationObject[part] = [value];
                    } else {
                        iterationObjectElement.push(value);
                    }
                } else {
                    iterationObject[part] = value;
                }
            }
        }
    }

    return object;
}

其他回答

如果你有多个相同名称的条目,例如如果你使用<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仍然不支持,因为它不支持ForEach或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 );
  }

我知道已经很晚了,但这是解决方案;

new URLSearchParams(new FormData(formElement)).toString()

这个可以作为正文发送。遗憾的是,它不是JSON。

下面是一种包含处理多个值的简单方法的方法。如果一个键以[]结尾,它将值组合成一个数组(就像在旧的php约定中一样),并从键中删除[]:

function formDataToJson(f) {
  return Object.fromEntries(Array.from(f.keys(), k =>
    k.endsWith('[]') ? [k.slice(0, -2), f.getAll(k)] : [k, f.get(k)]));
}

例如,如果你有:

const f = new FormData();
f.append("a", "1");
f.append("a", "2");
f.append("b[]", "3");
f.append("b[]", "4");
formDataToJson(f)  // --> produces {a: "1", b: ["3","4"]}

这篇文章已经有一年了……但是,我真的非常非常喜欢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);

一行高手版:

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表单扩展。

Since last time I wrote the previous second case, at work it came a case that the PHP form has checkboxes on multi-levels. I wrote a new case to support previous case and this one. I created a snippet to better showcase this case, the result show on the console for this demo, modify this to your need. Tried to optimize it the best I could without compromising performance, however, it compromise some human readability. It takes advantage that arrays are objects and variables pointing to arrays are kept as reference. No hotshot for this one, be my guest.

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>

可以通过使用FormData()对象来实现这一点。这个FormData对象将使用表单的当前键/值填充表单,使用每个元素的name属性填充键,使用它们提交的值填充值。它还将对文件输入内容进行编码。

例子:

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

});