Convertir un objet JS pour former des données

129

Comment puis-je convertir mon objet JS en FormData ?

La raison pour laquelle je veux faire cela est que j'ai un objet que j'ai construit à partir des ~ 100 valeurs de champ de formulaire.

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

On me demande maintenant d'ajouter la fonctionnalité de téléchargement de fichier à mon formulaire, ce qui, bien sûr, est impossible via JSON et je prévois donc de passer à FormData. Alors, y a-t-il un moyen de convertir mon objet JS en FormData?

Kamran Ahmed
la source
pouvez-vous partager votre travail / progrès?
Ritikesh
qu'en est-il de JSON.stringify ()?
Sunny Sharma
1
@Sunny - Cela produira un texte JSON dans une chaîne. Ce n'est pas un FormDataobjet.
Quentin
Oui, vous pouvez, vous pouvez ajouter aux objets formData.
adeneo
pouvez-vous nous montrer ce que vous entendez par FormData? un format spécifique?
Sunny Sharma le

Réponses:

156

Si vous avez un objet, vous pouvez facilement créer un objet FormData et ajouter les noms et les valeurs de cet objet à formData.

Vous n'avez posté aucun code, c'est donc un exemple général;

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

Il y a plus d'exemples dans la documentation sur MDN

adeneo
la source
4
@Lior - itemest un objet régulier créé par l'OP, il ne devrait donc pas avoir de propriétés qui ne lui soient pas propres, à moins que quelqu'un n'ait commis l'erreur de prototyper quelque chose sur le constructeur Object, auquel cas vous seriez dans un monde de problèmes , et ce n'est pas quelque chose dont nous devrions nous protéger.
adeneo
2
@Lior - il s'agit simplement d'ajouter les paires clé / valeur à FormData, l'ajout d'une propriété prototypée ne cassera rien, et l'utilisation Object.keysn'est pas la réponse, car vous ne devriez pas avoir à obtenir les clés sous forme de tableau, puis itérer sur les clés pour obtenir les valeurs, vous devriez utiliser une for..inboucle.
adeneo
2
Bien sûr, vous ne savez pas ce que le serveur attend ... car ... dans JS est problamtic, la solution ne doit pas être Object.keys (), cela pourrait être hasOwnProperty (), mais cela doit être au moins un avertissement.
Lior
3
@Lior - Si votre serveur tombe en panne lorsqu'il reçoit une autre paire clé / valeur dans une requête POST, vous le faites mal. Je pense que la réponse est bonne, et je ne vais pas la changer pour utiliser Object.keysou hasOwnProperty()car l'objet est affiché dans la question et ne devrait pas en avoir besoin. La raison pour laquelle vous voyez parfois hasOwnPropertyutilisé dans les plugins, etc. est que vous ne savez jamais ce que certaines personnes pourraient faire au Objectconstructeur, mais pour la plupart, les gens ne devraient pas avoir à tester les propriétés héritées des objets qu'ils ont créés, c'est un signe que vous faites probablement quelque chose de mal.
adeneo
5
@Lior avez-vous l'intention de construire des avions en paille ensuite, en espérant que cela attirerait plus de vrais avions qui laisseraient tomber de la nourriture du ciel? Il est important de comprendre pourquoi une vérification hasOwnProperty est utilisée, en disant simplement que les choses sont considérées comme des «meilleures pratiques» parce que vous lisez le livre de quelqu'un (supposant, Crockford) ne vous mène pas très loin, en essayant d'éduquer un autre membre So avec plus de 100 fois la réputation et 100 fois le nombre de réponses que vous avez n'aident pas beaucoup non plus. Aussi, nommez-vous une nouvelle bibliothèque tierce qui change le prototype? Ce message est d'une autre époque ...
Benjamin Gruenbaum
85

Avec ES6 et une approche de programmation plus fonctionnelle, la réponse de @ adeneo pourrait ressembler à ceci:

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

Et alternativement en utilisant .reduce()et les fonctions fléchées:

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

Cette fonction ajoute toutes les données de l'objet à FormData

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

Version 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
la source
Nice Keep it up
Vivek Doshi
Fonctionne très bien! Je vous remercie! J'ai dû aussi ajouter && !(data instanceof Blob)dans mon cas pour télécharger mes images
Clément Baconnier
Fonctionne bien pour moi, j'ai ajouté if (typeof data === 'object' && data! == null) {car il lançait une exception si la valeur est nulle
al000y
Pour la version ES6, j'ai ajouté && !(Array.isArray(data) && !data.length)la condition "si", sinon le tableau vide serait supprimé.
Mtxz
C'est du génie. J'ai très bien résolu mon problème.
chérie
15

Les autres réponses étaient incomplètes pour moi. Je suis parti de la réponse de @Vladimir Novopashin et je l'ai modifiée. Voici les choses dont j'avais besoin et le bug que j'ai trouvé:

  • Prise en charge du fichier
  • Prise en charge de la baie
  • Bug: Le fichier à l'intérieur d'un objet complexe doit être ajouté avec .propau lieu de [prop]. Par exemple, formData.append('photos[0][file]', file)ne fonctionnait pas sur Google Chrome, alors que formData.append('photos[0].file', file) travaillait
  • Ignorer certaines propriétés de mon objet

Le code suivant devrait fonctionner sur les navigateurs IE11 et Evergreen.

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
la source
2
La seule réponse prenant en charge les tableaux, les objets et les fichiers.
sotn le
Salut, pourquoi ajoutez-vous le fichier à la racine? Est-il possible de l'ajouter à l'enfant aussi?
Cedric Arnould
@CedricArnould Cela peut être un malentendu mais la méthode est récursive donc même si elle est écrite formData.append(root, data), cela ne signifie pas qu'elle est ajoutée à la racine.
Gudradain
Je comprends votre réponse, étrangement lorsque j'obtiens le résultat sur le serveur, j'ai une collection unique de fichiers et de données. Mais je peux voir dans un fichier le nom donner les informations à quel enfant il est connecté. Peut-être que le problème vient de .Net Core et de la façon dont il gère les fichiers de téléchargement. Merci pour votre réponse.
Cedric Arnould
J'essaye d'utiliser ceci mais il n'y a aucun exemple d'utilisation. le format attendu du paramètre ignoreList serait très utile.
jpro le
9

Essayez la fonction JSON.stringify comme ci-dessous

var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );
Udayraj Khuman
la source
1
C'est la meilleure façon d'y parvenir.
Rob
son garder ajouter le json après plusieurs fois de débogage d'erreur
Snow Bases
7

J'ai eu un scénario dans lequel le JSON imbriqué devait être sérialisé de manière linéaire pendant la construction des données de formulaire, car c'est ainsi que le serveur attend des valeurs. Donc, j'ai écrit une petite fonction récursive qui traduit le JSON qui est comme ceci:

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

Dans quelque chose comme ça:

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

Le serveur accepterait les données de formulaire qui sont dans ce format converti.

Voici la fonction:

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

Invocation:

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

J'utilise testJSON uniquement pour voir les résultats convertis car je ne pourrais pas extraire le contenu de form_data. Appel post AJAX:

        $.ajax({
            type: "POST",
            url: somePostURL,
            data: form_data,
            processData : false,
            contentType : false,
            success: function (data) {
            },
            error: function (e) {
            }
        });
Raj Pawan Gumdal
la source
Salut Raj, qu'en est-il des tableaux? Dites que vous disposez de plusieurs adresses de facturation. Comment résoudriez-vous cela?
Sam
1
J'ai trouvé la réponse! Votre message est vraiment utile. Merci!
Sam
3

Désolé pour une réponse tardive, mais j'avais du mal avec cela car Angular 2 ne prend actuellement pas en charge le téléchargement de fichiers. Donc, la façon de le faire était d'envoyer un XMLHttpRequestavec FormData. Alors, j'ai créé une fonction pour le faire. J'utilise Typescript . Pour le convertir en Javascript, supprimez simplement la déclaration des types de données.

/**
     * 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
la source
Vous devez inclure des index de tableau au lieu de []propriétés d'objet for dans un tableau numérique pour rester intact
Vicary
1

Version 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
la source
1
Tout d'abord, namespaceun mot clé réservé dans TypeScript( typescriptlang.org/docs/handbook/namespaces.html et github.com/Microsoft/TypeScript/issues/… ). De plus, il semble que vous ayez oublié de vous en occuper Filedepuis le dernier elsetestament append "[object File]"au formData.
Jyrkka
1

Vous pouvez simplement installer qs:

npm i qs

Importez simplement:

import qs from 'qs'

Passer l'objet à qs.stringify():

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

qs.stringify(item)
Balaj Khan
la source
1

Récursivement

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
la source
meilleure réponse à mon humble avis
ling
0

Cette méthode convertit un objet JS en 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());
}

Monkey Monk
la source
Object.entries(value).forEach((v, k) => acc.append(`${key}[${v[0]}]`, v[1]));
Corrigez simplement l'
0

Dans mon cas, mon objet avait également une propriété qui était un tableau de fichiers. Puisqu'ils sont binaires, ils doivent être traités différemment - l'index n'a pas besoin de faire partie de la clé. J'ai donc modifié la réponse de @Vladimir Novopashin et de @ 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
la source
0

Je l'ai utilisé pour publier mes données d'objet en tant que données de formulaire.

const encodeData = require('querystring');

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

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

Peut-être que vous recherchez ceci, un code qui reçoit votre objet javascript, créez un objet FormData à partir de celui-ci, puis POST-le sur votre serveur à l'aide de la nouvelle 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
la source
0

Je fais référence à cela d' après la réponse de Gudradain . Je l'édite un peu au format 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
la source
0

Je suis peut-être un peu en retard à la fête, mais c'est ce que j'ai créé pour convertir un objet singulier 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;
}

Comment ça marche? Il convertira et retournera toutes les propriétés attendues des objets File que vous avez définis dans la liste des ignorés (2ème argument. Si quelqu'un pouvait me dire une meilleure façon de déterminer cela, cela aiderait!) En une chaîne json en utilisantJSON.stringify . Ensuite, sur votre serveur, il vous suffira de le reconvertir en objet JSON.

Exemple:

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

Je suis encore un peu nouveau dans les requêtes Http et JavaScript, donc tout commentaire serait très apprécié!

Gibbu
la source