Convertir objeto JS en datos de formulario

128

¿Cómo puedo convertir mi objeto JS a FormData ?

La razón por la que quiero hacer esto es que tengo un objeto que construí a partir de ~ 100 valores de campo de formulario.

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

Ahora se me pide que agregue la funcionalidad de carga de archivos a mi formulario, lo cual, por supuesto, es imposible a través de JSON y, por lo tanto, estoy planeando pasar a FormData. Entonces, ¿hay alguna forma en que pueda convertir mi objeto JS FormData?

Kamran Ahmed
fuente
¿Puedes compartir tu trabajo / progreso?
Ritikesh
¿qué tal JSON.stringify ()?
Sunny Sharma
1
@Sunny: eso producirá un texto JSON en una cadena. Eso no es un FormDataobjeto.
Quentin
Sí, puede agregar objetos a formData.
adeneo
¿Puede mostrarnos qué quiere decir con FormData? algún formato específico?
Sunny Sharma

Respuestas:

153

Si tiene un objeto, puede crear fácilmente un objeto FormData y agregar los nombres y valores de ese objeto a formData.

No ha publicado ningún código, por lo que es un ejemplo general;

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

Hay más ejemplos en la documentación sobre MDN

adeneo
fuente
3
@Lior: itemes un objeto regular creado por el OP, por lo que no debería tener propiedades que no sean las suyas, a menos que alguien haya cometido el error de crear un prototipo de algo en el constructor del objeto, en cuyo caso estaría en un mundo de problemas. , y no es algo contra lo que debamos tener que protegernos.
adeneo
2
@Lior: es solo agregar los pares clave / valor a FormData, agregar una propiedad prototipada no romperá nada, y usar Object.keysno es la respuesta, ya que no debería tener que obtener las claves como una matriz, luego iterar sobre las claves para obtener los valores, debería utilizar un for..inbucle.
adeneo
2
Por supuesto que lo hará, no sabe lo que espera el servidor ... porque ... en JS es problemático, la solución no tiene que ser Object.keys (), podría ser hasOwnProperty (), pero debe ser al menos una advertencia.
Lior
3
@Lior: si su servidor se rompe cuando recibe un par clave / valor más en una solicitud POST, lo está haciendo mal. Creo que la respuesta está bien y no la voy a cambiar para usar Object.keyso hasOwnProperty()ya que el objeto está publicado en la pregunta y no debería necesitar ninguno de esos. La razón por la que a veces se hasOwnPropertyusa en complementos, etc.es porque nunca se sabe lo que algunas personas podrían hacer con el Objectconstructor, pero en su mayor parte, las personas no deberían tener que probar las propiedades heredadas en los objetos que han creado, eso es una señal de que probablemente estés haciendo algo mal.
adeneo
5
@Lior, ¿tienes la intención de construir aviones con paja a continuación, con la esperanza de que atraiga más aviones reales que arrojen comida del cielo? Es importante comprender por qué se usa una verificación hasOwnProperty, simplemente decir que las cosas se consideran "mejores prácticas" porque leer el libro de alguien (adivinar, Crockford) no lo lleva muy lejos, tratando de educar a un miembro de So con más de 100 veces la reputación y 100 veces la cantidad de respuestas que tienes tampoco ayudan mucho a tu punto. Además, nombre una nueva biblioteca de terceros que cambie el prototipo. Esa publicación es de una época diferente ...
Benjamin Gruenbaum
83

Con ES6 y un enfoque de programación más funcional, la respuesta de @ adeneo podría verse así:

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

Y alternativamente usando .reduce()y funciones de flecha:

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

Esta función agrega todos los datos del objeto a FormData

Versión 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)

Versión de 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
fuente
Agradable Sigue así
Vivek Doshi
¡Funciona muy bien! ¡Gracias! También tuve que agregar && !(data instanceof Blob)en mi caso para cargar mis imágenes
Clément Baconnier
Funciona bien para mí, agregué if (typeof data === 'object' && data! == null) {porque estaba lanzando una excepción si el valor es nulo
al000y
Para la versión ES6, agregué && !(Array.isArray(data) && !data.length)la condición "si" o se eliminaría la matriz vacía.
Mtxz
14

Las otras respuestas fueron incompletas para mí. Comencé desde la respuesta de @Vladimir Novopashin y la modifiqué. Aquí están las cosas que necesitaba y el error que encontré:

  • Soporte para archivo
  • Soporte para matriz
  • Error: el archivo dentro del objeto complejo debe agregarse con en .proplugar de [prop]. Por ejemplo, formData.append('photos[0][file]', file)no funcionó en Google Chrome, mientras formData.append('photos[0].file', file)trabajaba
  • Ignorar algunas propiedades en mi objeto

El siguiente código debería funcionar en IE11 y navegadores perennes.

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
fuente
1
La única respuesta que admite matrices, objetos y archivos.
sábado
Hola, ¿por qué agregas el archivo a la raíz? ¿Es posible agregarlo al niño también?
Cedric Arnould
@CedricArnould Puede ser un malentendido, pero el método es recursivo, por lo que incluso si está escrito formData.append(root, data), no significa que se haya agregado a la raíz.
Gudradain
Entiendo su respuesta, curiosamente cuando obtengo el resultado en el servidor, tengo una colección única de archivos y datos. Pero puedo ver en un archivo el nombre que da la información a qué niño está conectado. Quizás el problema provenga de .Net Core y cómo administra los archivos de carga. Gracias por tu respuesta.
Cedric Arnould
Estoy tratando de usar esto pero no hay un ejemplo de uso. el formato esperado del parámetro ignoreList sería bastante útil.
jpro
8

Pruebe la función JSON.stringify como se muestra a continuación

var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );
Udayraj Khuman
fuente
1
Esta es la mejor forma de lograrlo.
Rob
su mantener anexar el json después de varias veces de depuración de errores
Snow Bases
7

Tuve un escenario en el que JSON anidado tenía que serializarse de forma lineal mientras se construían los datos del formulario, ya que así es como el servidor espera los valores. Entonces, escribí una pequeña función recursiva que traduce el JSON que es así:

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

En algo como esto:

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

El servidor aceptará datos de formulario que estén en este formato convertido.

Aquí está la función:

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

Invocación:

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

Estoy usando testJSON solo para ver los resultados convertidos, ya que no podría extraer el contenido de form_data. Llamada posterior a AJAX:

        $.ajax({
            type: "POST",
            url: somePostURL,
            data: form_data,
            processData : false,
            contentType : false,
            success: function (data) {
            },
            error: function (e) {
            }
        });
Raj Pawan Gumdal
fuente
Hola Raj, ¿qué tal las matrices? Digamos que tiene más de 1 dirección de facturación. ¿Cómo arreglarías eso?
Sam
1
¡He encontrado la respuesta! Tu publicación es realmente útil. ¡Gracias!
Sam
3

Perdón por una respuesta tardía, pero estaba luchando con esto ya que Angular 2 actualmente no admite la carga de archivos. Por lo tanto, la manera de hacerlo que estaba enviando una XMLHttpRequestcon FormData. Entonces, creé una función para hacerlo. Estoy usando Typecript . Para convertirlo a Javascript, simplemente elimine la declaración de tipos de datos.

/**
     * 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
fuente
Debe incluir índices de matriz en lugar de []para las propiedades del objeto dentro de una matriz numérica para permanecer intacto
Vicary
1

Versión de 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
fuente
1
En primer lugar, namespacees una palabra clave reservada en TypeScript( typescriptlang.org/docs/handbook/namespaces.html y github.com/Microsoft/TypeScript/issues/… ). Además, parece que se le olvidó tratar Filedesde la última elsevoluntad append "[object File]"hasta el formData.
Jyrkka
1

Simplemente puede instalar qs:

npm i qs

Simplemente importe:

import qs from 'qs'

Pasar objeto a qs.stringify():

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

qs.stringify(item)
Balaj Khan
fuente
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
fuente
mejor respuesta en mi humilde opinión
ling
0

Este método convierte un objeto JS en un 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());
}

Monje mono
fuente
Simplemente corrija la llamada de entradas de objetos anidados de iteración: Object.entries(value).forEach((v, k) => acc.append(`${key}[${v[0]}]`, v[1]));
heber gentilin
0

En mi caso, mi objeto también tenía una propiedad que era una matriz de archivos. Dado que son binarios, deben tratarse de manera diferente: el índice no necesita ser parte de la clave. Así que modifiqué la respuesta de @Vladimir Novopashin y @ 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
fuente
0

Usé esto para publicar mis datos de objeto como datos de formulario.

const encodeData = require('querystring');

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

console.log(object);
console.log(encodeData.stringify(object));
Yunus ER
fuente
0

Tal vez esté buscando esto, un código que recibe su objeto javascript, crea un objeto FormData a partir de él y luego lo envía a su servidor usando la nueva 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 Rodríguez González
fuente
0

Hago referencia a esto de la respuesta de Gudradain . Lo edito un poco en formato Typecript.

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
fuente
0

Puede que llegue un poco tarde a la fiesta, pero esto es lo que he creado para convertir un objeto singular en 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? Convertirá y devolverá todas las propiedades, excepto los objetos de archivo que haya configurado en la lista de ignorados (segundo argumento. Si alguien pudiera decirme una mejor manera de determinar esto, ¡eso ayudaría!) En una cadena json usandoJSON.stringify . Luego, en su servidor, solo tendrá que convertirlo nuevamente en un objeto JSON.

Ejemplo:

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

Todavía soy un poco nuevo en las solicitudes de Http y JavaScript, por lo que cualquier comentario sería muy apreciado.

Gibbu
fuente