Konwertuj obiekt JS na dane formularza


128

Jak mogę przekonwertować mój obiekt JS na FormData?

Powodem, dla którego chcę to zrobić, jest to, że mam obiekt, który utworzyłem z ~ 100 wartości pól formularza.

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

Teraz jestem proszony o dodanie funkcji przesyłania plików do mojego formularza, co oczywiście jest niemożliwe przez JSON, więc planuję przejść do FormData. Czy jest więc jakiś sposób, w jaki mogę przekonwertować mój obiekt JS na FormData?


czy możesz podzielić się swoją pracą / postępem?
Ritikesh

a co z JSON.stringify ()?
Sunny Sharma

1
@Sunny - to utworzy tekst JSON w ciągu. To nie jest FormDataprzedmiot.
Quentin

Tak, możesz dołączyć do obiektów formData.
adeneo

czy możesz nam pokazać, co rozumiesz przez FormData? jakiś konkretny format?
Sunny Sharma

Odpowiedzi:


153

Jeśli masz obiekt, możesz łatwo utworzyć obiekt FormData i dołączyć nazwy i wartości z tego obiektu do formData.

Nie opublikowałeś żadnego kodu, więc jest to ogólny przykład;

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

Więcej przykładów znajduje się w dokumentacji dotyczącej MDN


3
@Lior - itemto zwykły obiekt stworzony przez OP, więc nie powinien mieć żadnych właściwości, które nie są jego własnością, chyba że ktoś popełnił błąd prototypowania czegoś na konstruktorze Object, w takim przypadku byłbyś w świecie kłopotów i nie jest to coś, przed czym powinniśmy się chronić.
adeneo

2
@Lior - to po prostu dodanie par klucz / wartość do FormData, dodanie prototypowanej właściwości niczego nie zepsuje, a użycie Object.keysnie jest odpowiedzią, ponieważ nie powinieneś mieć kluczy jako tablicy, a następnie iteruj po kluczach, aby uzyskać wartości, powinieneś używać for..inpętli.
adeneo

2
Oczywiście, że tak, nie wiesz, czego oczekuje serwer ... bo ... w JS jest problematyczne, rozwiązaniem nie musi być Object.keys (), może to być hasOwnProperty (), ale musi to być przynajmniej ostrzeżenie.
Lior

3
@Lior - Jeśli twój serwer zepsuje się, gdy otrzyma jeszcze jedną parę klucz / wartość w żądaniu POST, robisz to źle. Myślę, że odpowiedź jest w porządku i nie zamierzam jej zmieniać, aby używać Object.keyslub hasOwnProperty()ponieważ obiekt jest umieszczony w pytaniu i nie powinien potrzebować żadnego z nich. Powodem, dla którego czasami widzisz hasOwnPropertyużywane we wtyczkach itp., Jest to, że nigdy nie wiesz, co niektórzy ludzie mogą zrobić z Objectkonstruktorem, ale w większości ludzie nie powinni być zmuszeni do testowania dziedziczonych właściwości obiektów, które utworzyli, to znak, że prawdopodobnie robisz coś złego.
adeneo

5
@Lior, czy zamierzasz w następnej kolejności budować samoloty ze słomy, mając nadzieję, że przyciągnie to więcej prawdziwych samolotów, które zrzucą jedzenie z nieba? Ważne jest, aby zrozumieć, dlaczego używane jest sprawdzenie hasOwnProperty, samo mówienie rzeczy są uważane za „najlepszą praktykę”, ponieważ przeczytanie czyjejś książki (zgadywanie, książka Crockforda) nie prowadzi cię zbyt daleko, próbując wykształcić innego członka So z ponad 100 razy reputacja i 100-krotność liczby odpowiedzi również nie pomagają w tym zbytnio. Wymień też jedną nową bibliotekę innej firmy, która zmienia prototyp? Ten post jest z innego czasu ...
Benjamin Gruenbaum

84

Dzięki ES6 i bardziej funkcjonalnemu podejściu do programowania odpowiedź @ adeneo mogłaby wyglądać następująco:

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

Alternatywnie używając .reduce()i funkcji strzałek:

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

44

Ta funkcja dodaje wszystkie dane z obiektu do FormData

Wersja ES6 od @ 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)

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

Miło Tak trzymaj
Vivek Doshi

Działa bardzo dobrze! Dziękuję Ci! Musiałem również dodać && !(data instanceof Blob)w moim przypadku, aby przesłać moje zdjęcia
Clément Baconnier

U mnie działa dobrze, dodałem if (typeof data === 'object' && data! == null) {ponieważ rzucał wyjątek, jeśli wartość jest zerowa
prawie 1000y

Dla wersji ES6 dodałem && !(Array.isArray(data) && !data.length)warunek "if", inaczej pusta tablica zostanie usunięta.
Mtxz

14

Pozostałe odpowiedzi były dla mnie niepełne. Zacząłem od odpowiedzi @Vladimira Novopashina i zmodyfikowałem ją. Oto rzeczy, których potrzebowałem i znalazłem błąd:

  • Wsparcie dla pliku
  • Wsparcie dla macierzy
  • Błąd: plik wewnątrz złożonego obiektu należy dodać .propzamiast [prop]. Na przykład formData.append('photos[0][file]', file)nie działał w Google Chrome, podczas gdy formData.append('photos[0].file', file)działał
  • Zignoruj ​​niektóre właściwości w moim obiekcie

Poniższy kod powinien działać na IE11 i wiecznie zielonych przeglądarkach.

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

1
Jedyna odpowiedź obsługująca tablice, obiekty i pliki.
sotn

Cześć, dlaczego dodajesz plik do katalogu głównego? Czy można go również dodać dziecku?
Cedric Arnould

@CedricArnould Może to być nieporozumienie, ale metoda jest rekurencyjna, więc nawet jeśli jest napisana formData.append(root, data), nie oznacza to, że jest dodana do katalogu głównego.
Gudradain

Rozumiem twoją odpowiedź, o dziwo, gdy otrzymuję wynik na serwerze, mam unikalną kolekcję plików i danych. Ale widzę w pliku nazwę, która zawiera informacje, z którym dzieckiem jest połączona. Może problem pochodzi z .Net Core i tego, jak zarządza przesyłanymi plikami. Dziękuję za odpowiedź.
Cedric Arnould

Próbuję tego użyć, ale nie ma przykładu użycia. oczekiwany format parametru ignoreList byłby całkiem pomocny.
jpro

8

Wypróbuj funkcję JSON.stringify, jak poniżej

var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );

1
To najlepszy sposób, aby to osiągnąć.
Rob

jego zachowywanie dołącza plik json po kilkukrotnym debugowaniu błędów
Snow Bases

7

Miałem scenariusz, w którym zagnieżdżone JSON musiało być serializowane w sposób liniowy podczas konstruowania danych formularza, ponieważ w ten sposób serwer oczekuje wartości. Więc napisałem małą funkcję rekurencyjną, która tłumaczy JSON, który wygląda tak:

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

W coś takiego:

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

Serwer akceptuje dane formularzy w tym przekonwertowanym formacie.

Oto funkcja:

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

Wezwanie:

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

Używam testJSON tylko po to, aby zobaczyć przekonwertowane wyniki, ponieważ nie byłbym w stanie wyodrębnić zawartości form_data. Połączenie pocztowe AJAX:

        $.ajax({
            type: "POST",
            url: somePostURL,
            data: form_data,
            processData : false,
            contentType : false,
            success: function (data) {
            },
            error: function (e) {
            }
        });

Cześć Raj, a co z tablicami? Powiedz, że masz więcej niż 1 adres rozliczeniowy. Jak byś to naprawił?
Sam

1
Znalazłem odpowiedź! Twój post jest naprawdę pomocny. Dzięki!
Sam

3

Przepraszam za spóźnioną odpowiedź, ale walczyłem z tym, ponieważ Angular 2 obecnie nie obsługuje przesyłania plików. Więc sposobem na to było wysłanie XMLHttpRequestz FormData. Stworzyłem więc funkcję, która to robi. Używam Typescript . Aby przekonwertować go na JavaScript, po prostu usuń deklarację typów danych.

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

Musisz uwzględnić indeksy tablic zamiast a []dla właściwości obiektu wewnątrz tablicy numerycznej, aby pozostać nienaruszonym
Vicary

1

Wersja 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
Przede wszystkim, namespacejest kluczowe w zarezerwowanym TypeScript( typescriptlang.org/docs/handbook/namespaces.html i github.com/Microsoft/TypeScript/issues/... ). Wydaje się również, że zapomniałeś sobie z tym poradzić Fileod ostatniej elsewoli append "[object File]"do formData.
Jyrkka

1

Możesz po prostu zainstalować qs:

npm i qs

Po prostu zaimportuj:

import qs from 'qs'

Przekaż obiekt qs.stringify():

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

qs.stringify(item)

1

Rekurencyjnie

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>


najlepsza odpowiedź imho
ling

0

Ta metoda konwertuje obiekt JS na 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());
}


Po prostu napraw iterację zagnieżdżonych wpisów obiektów wywołanie: Object.entries(value).forEach((v, k) => acc.append(`${key}[${v[0]}]`, v[1]));
heber gentilin

0

W moim przypadku mój obiekt miał również właściwość, która była tablicą plików. Ponieważ są binarne, powinny być traktowane inaczej - indeks nie musi być częścią klucza. Więc zmodyfikowałem odpowiedź @Vladimira Novopashina i @ 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

Użyłem tego do przesyłania moich danych obiektu jako danych formularza.

const encodeData = require('querystring');

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

console.log(object);
console.log(encodeData.stringify(object));

0

Może szukasz tego, kodu, który otrzyma Twój obiekt javascript, utwórz z niego obiekt FormData , a następnie POST na swój serwer za pomocą nowego Fetch API :

    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

Odwołuję się do tego z odpowiedzi Gudradaina . Trochę go edytuję w formacie 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

Mogę się trochę spóźnić na imprezę, ale to właśnie stworzyłem, aby przekonwertować pojedynczy obiekt na 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;
}

Jak to działa? Przekonwertuje i zwróci wszystkie oczekiwane właściwości obiektów File, które ustawiłeś na liście ignorowanych (drugi argument. Gdyby ktoś mógł mi powiedzieć lepszy sposób określenia tego, który by pomógł!) Do łańcucha json za pomocąJSON.stringify . Następnie na swoim serwerze wystarczy przekonwertować go z powrotem na obiekt JSON.

Przykład:

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

Wciąż nie mam doświadczenia z żądaniami HTTP i JavaScriptem, więc wszelkie opinie będą bardzo mile widziane!


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.