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?


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


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

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

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

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

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

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

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


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)

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>


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


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

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

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

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

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é!


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.